From 3bcad110d0173e12514a98d58c76f8de9d89881c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Mon, 2 Sep 2024 21:00:43 -0500 Subject: [PATCH] [core] Introduce QgsStackedDiagramRenderer for rendering stacked diagrams. This allows subdiagrams to define their own sizes and legends; add tests for a stacked diagram composed of mixed renderers (single category and linearly interpolated categories), as well as nested stacked diagrams. --- .../core/auto_additions/qgsstackeddiagram.py | 4 - .../diagram/qgsstackeddiagram.sip.in | 44 -- .../auto_generated/qgsdiagramrenderer.sip.in | 132 ++++- .../core/auto_additions/qgsstackeddiagram.py | 4 - .../diagram/qgsstackeddiagram.sip.in | 44 -- .../auto_generated/qgsdiagramrenderer.sip.in | 132 ++++- src/core/diagram/qgsstackeddiagram.cpp | 191 +------ src/core/diagram/qgsstackeddiagram.h | 39 -- src/core/qgsdiagramrenderer.cpp | 466 ++++++++++-------- src/core/qgsdiagramrenderer.h | 127 ++++- src/core/vector/qgsvectorlayer.cpp | 6 + src/gui/vector/qgsdiagramproperties.cpp | 115 ++--- src/gui/vector/qgsdiagramproperties.h | 9 +- .../vector/qgsstackeddiagramproperties.cpp | 53 +- src/gui/vector/qgsstackeddiagramproperties.h | 5 - tests/code_layout/acceptable_missing_doc.py | 1 + tests/src/core/testqgsstackeddiagram.cpp | 457 +++++++++++++---- .../expected_stackeddiagramsnested.png | Bin 0 -> 1022152 bytes .../expected_stackedpiehistogram.png | Bin 0 -> 1022152 bytes 19 files changed, 1061 insertions(+), 768 deletions(-) create mode 100644 tests/testdata/control_images/stackeddiagrams/expected_stackeddiagramsnested/expected_stackeddiagramsnested.png create mode 100644 tests/testdata/control_images/stackeddiagrams/expected_stackedpiehistogram/expected_stackedpiehistogram.png diff --git a/python/PyQt6/core/auto_additions/qgsstackeddiagram.py b/python/PyQt6/core/auto_additions/qgsstackeddiagram.py index 350b840310ed..4d3fc2a8efa2 100644 --- a/python/PyQt6/core/auto_additions/qgsstackeddiagram.py +++ b/python/PyQt6/core/auto_additions/qgsstackeddiagram.py @@ -3,7 +3,3 @@ QgsStackedDiagram.__group__ = ['diagram'] except NameError: pass -try: - QgsStackedDiagram.DiagramData.__group__ = ['diagram'] -except NameError: - pass diff --git a/python/PyQt6/core/auto_generated/diagram/qgsstackeddiagram.sip.in b/python/PyQt6/core/auto_generated/diagram/qgsstackeddiagram.sip.in index 33c107d8aea2..d8d7860ca213 100644 --- a/python/PyQt6/core/auto_generated/diagram/qgsstackeddiagram.sip.in +++ b/python/PyQt6/core/auto_generated/diagram/qgsstackeddiagram.sip.in @@ -25,55 +25,11 @@ A diagram composed of several subdiagrams, located side by side. %End public: - struct DiagramData - { - QgsDiagram *diagram; - QgsDiagramSettings *settings; - }; - QgsStackedDiagram(); virtual QgsStackedDiagram *clone() const /Factory/; - void addSubDiagram( QgsDiagram *diagram, QgsDiagramSettings *s ); -%Docstring -Adds a subdiagram to the stacked diagram object along with its corresponding settings. - -:param diagram: subdiagram to be added to the stacked diagram -:param s: subdiagram settings - Subdiagrams added first will appear more to the left (if stacked diagram is horizontal), - or more to the top (if stacked diagram is vertical). -%End - - int subDiagramCount() const; -%Docstring -Returns the number of subdiagrams that this stacked diagram is composed of. -%End - - QString subDiagramType( int index ) const; -%Docstring -Returns the type of the subdiagram located at a given ``index``. -%End - - QList< QgsDiagram * > subDiagrams( const QgsDiagramSettings &s ) const; -%Docstring -Returns an ordered list with the subdiagrams of the stacked diagram object. -If the stacked diagram orientation is vertical, the list is returned backwards. - -:param s: stacked diagram settings -%End - - QgsDiagramSettings *subDiagramSettings( const QgsDiagram *diagram ) const; -%Docstring -Returns the settings associated to the ``diagram``. -%End - - QgsDiagramSettings *subDiagramSettings( int index ) const; -%Docstring -Returns the diagram settings for the diagram located at a given ``index``. -%End - void subDiagramPosition( QPointF &newPos, const QgsRenderContext &c, const QgsDiagramSettings &s, const QgsDiagramSettings &subSettings ); %Docstring Calculates the position for the next subdiagram, updating the ``newPos`` object. diff --git a/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in b/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in index e198ba6a7cba..0186aa3cf9f7 100644 --- a/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in +++ b/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in @@ -680,6 +680,8 @@ Evaluates and returns the diagram settings relating to a diagram for a specific sipType = sipType_QgsSingleCategoryDiagramRenderer; else if ( sipCpp->rendererName() == QLatin1String( "LinearlyInterpolated" ) ) sipType = sipType_QgsLinearlyInterpolatedDiagramRenderer; + else if ( sipCpp->rendererName() == QLatin1String( "Stacked" ) ) + sipType = sipType_QgsStackedDiagramRenderer; else sipType = NULL; %End @@ -712,7 +714,7 @@ Returns the set of any fields required for diagram rendering :param context: expression context the diagrams will be drawn using %End - void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; + virtual void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; %Docstring Renders the diagram for a specified feature at a specific position in the passed render context. %End @@ -769,23 +771,21 @@ Sets whether the renderer will show legend items for diagram attributes. protected: QgsDiagramRenderer( const QgsDiagramRenderer &other ); - virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = 0 ) const = 0; + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const = 0; %Docstring Returns diagram settings for a feature (or ``False`` if the diagram for the feature is not to be rendered). Used internally within :py:func:`~QgsDiagramRenderer.renderDiagram` :param feature: the feature :param c: render context :param s: out: diagram settings for the feature -:param subDiagram: subDiagram object for stacked diagram case %End - virtual QSizeF diagramSize( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagram *subDiagram = 0 ) const = 0; + virtual QSizeF diagramSize( const QgsFeature &feature, const QgsRenderContext &c ) const = 0; %Docstring Returns size of the diagram (in painter units) or an invalid size in case of error :param feature: the feature :param c: render context -:param subDiagram: subDiagram object for stacked diagram case %End void convertSizeToMapUnits( QSizeF &size, const QgsRenderContext &context ) const; @@ -804,13 +804,6 @@ Returns the paint device dpi (or -1 in case of error Reads internal QgsDiagramRenderer state from a DOM element. .. seealso:: _writeXml -%End - - void _readXmlSubdiagrams( const QDomElement &elem, const QgsReadWriteContext &context ); -%Docstring -Reads Stacked Diagram's subdiagram state from a DOM element. - -.. seealso:: _writeXmlSubDiagrams %End void _writeXml( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; @@ -820,12 +813,6 @@ Writes internal QgsDiagramRenderer diagram state to a DOM element. .. seealso:: _readXml %End - void _writeXmlSubDiagrams( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; -%Docstring -Writes Stacked Diagram's subdiagram state to a DOM element. - -.. seealso:: _readXmlSubdiagrams -%End }; @@ -864,10 +851,10 @@ Renders the diagrams for all features with the same settings protected: - virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = 0 ) const; + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const; - virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c, QgsDiagram *subDiagram = 0 ) const; + virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const; }; @@ -957,14 +944,115 @@ Returns configuration of appearance of legend. Will return ``None`` if no config %End protected: - virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = 0 ) const; + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const; + + + virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const; + + +}; + +class QgsStackedDiagramRenderer : QgsDiagramRenderer +{ +%Docstring(signature="appended") +Renders diagrams using mixed diagram render types. The size of +the rendered diagram is given by a combination of subrenderers. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsdiagramrenderer.h" +%End + public: + QgsStackedDiagramRenderer(); + + virtual QgsStackedDiagramRenderer *clone() const /Factory/; + + + virtual QSizeF sizeMapUnits( const QgsFeature &feature, const QgsRenderContext &c ) const; +%Docstring +Returns size of the diagram for a feature in map units. Returns an invalid QSizeF in case of error +%End + + virtual void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; +%Docstring +Renders the diagram for a specified feature at a specific position in the +passed render context, taking all renderers and their own diagrams into account. +Diagram rendering is delegated to renderer's diagram. +%End + + virtual QList diagramSettings() const; + +%Docstring +Returns list with all diagram settings in the renderer +%End + void setDiagramSettings( const QgsDiagramSettings &s ); + + virtual QList diagramAttributes() const; + + + + virtual QString rendererName() const; + + virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context ); + + virtual void writeXml( QDomElement &layerElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; + + + void _readXmlSubRenderers( const QDomElement &elem, const QgsReadWriteContext &context ); +%Docstring +Reads stacked renderers state from a DOM element. + +.. seealso:: _writeXmlSubRenderers +%End - virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c, QgsDiagram *subDiagram = 0 ) const; + void _writeXmlSubRenderers( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; +%Docstring +Writes stacked renderers state to a DOM element. + +.. seealso:: _readXmlSubRenderers +%End + + virtual QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const /Factory/; + + + QList< QgsDiagramRenderer * > renderers() const; +%Docstring +Returns an ordered list with the renderers of the stacked renderer object. +If the stacked diagram orientation is vertical, the list is returned backwards. +%End + + void addRenderer( QgsDiagramRenderer *renderer ); +%Docstring +Adds a renderer to the stacked renderer object. + +:param renderer: diagram renderer to be added to the stacked renderer + Renderers added first will render their diagrams first, i.e., more to + the left (horizontal mode) or more to the top (vertical mode). +%End + + const QgsDiagramRenderer *renderer( const int index ) const; +%Docstring +Returns the renderer at the given ``index``. +@param index index of the disired renderer in the stacked renderer +%End + + int rendererCount() const; +%Docstring +Returns the number of renderers that this stacked renderer is composed of. +%End + + protected: + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const; + + virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const; }; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/core/auto_additions/qgsstackeddiagram.py b/python/core/auto_additions/qgsstackeddiagram.py index 350b840310ed..4d3fc2a8efa2 100644 --- a/python/core/auto_additions/qgsstackeddiagram.py +++ b/python/core/auto_additions/qgsstackeddiagram.py @@ -3,7 +3,3 @@ QgsStackedDiagram.__group__ = ['diagram'] except NameError: pass -try: - QgsStackedDiagram.DiagramData.__group__ = ['diagram'] -except NameError: - pass diff --git a/python/core/auto_generated/diagram/qgsstackeddiagram.sip.in b/python/core/auto_generated/diagram/qgsstackeddiagram.sip.in index 33c107d8aea2..d8d7860ca213 100644 --- a/python/core/auto_generated/diagram/qgsstackeddiagram.sip.in +++ b/python/core/auto_generated/diagram/qgsstackeddiagram.sip.in @@ -25,55 +25,11 @@ A diagram composed of several subdiagrams, located side by side. %End public: - struct DiagramData - { - QgsDiagram *diagram; - QgsDiagramSettings *settings; - }; - QgsStackedDiagram(); virtual QgsStackedDiagram *clone() const /Factory/; - void addSubDiagram( QgsDiagram *diagram, QgsDiagramSettings *s ); -%Docstring -Adds a subdiagram to the stacked diagram object along with its corresponding settings. - -:param diagram: subdiagram to be added to the stacked diagram -:param s: subdiagram settings - Subdiagrams added first will appear more to the left (if stacked diagram is horizontal), - or more to the top (if stacked diagram is vertical). -%End - - int subDiagramCount() const; -%Docstring -Returns the number of subdiagrams that this stacked diagram is composed of. -%End - - QString subDiagramType( int index ) const; -%Docstring -Returns the type of the subdiagram located at a given ``index``. -%End - - QList< QgsDiagram * > subDiagrams( const QgsDiagramSettings &s ) const; -%Docstring -Returns an ordered list with the subdiagrams of the stacked diagram object. -If the stacked diagram orientation is vertical, the list is returned backwards. - -:param s: stacked diagram settings -%End - - QgsDiagramSettings *subDiagramSettings( const QgsDiagram *diagram ) const; -%Docstring -Returns the settings associated to the ``diagram``. -%End - - QgsDiagramSettings *subDiagramSettings( int index ) const; -%Docstring -Returns the diagram settings for the diagram located at a given ``index``. -%End - void subDiagramPosition( QPointF &newPos, const QgsRenderContext &c, const QgsDiagramSettings &s, const QgsDiagramSettings &subSettings ); %Docstring Calculates the position for the next subdiagram, updating the ``newPos`` object. diff --git a/python/core/auto_generated/qgsdiagramrenderer.sip.in b/python/core/auto_generated/qgsdiagramrenderer.sip.in index 2abf1b0c6d01..b946b658e4f2 100644 --- a/python/core/auto_generated/qgsdiagramrenderer.sip.in +++ b/python/core/auto_generated/qgsdiagramrenderer.sip.in @@ -680,6 +680,8 @@ Evaluates and returns the diagram settings relating to a diagram for a specific sipType = sipType_QgsSingleCategoryDiagramRenderer; else if ( sipCpp->rendererName() == QLatin1String( "LinearlyInterpolated" ) ) sipType = sipType_QgsLinearlyInterpolatedDiagramRenderer; + else if ( sipCpp->rendererName() == QLatin1String( "Stacked" ) ) + sipType = sipType_QgsStackedDiagramRenderer; else sipType = NULL; %End @@ -712,7 +714,7 @@ Returns the set of any fields required for diagram rendering :param context: expression context the diagrams will be drawn using %End - void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; + virtual void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; %Docstring Renders the diagram for a specified feature at a specific position in the passed render context. %End @@ -769,23 +771,21 @@ Sets whether the renderer will show legend items for diagram attributes. protected: QgsDiagramRenderer( const QgsDiagramRenderer &other ); - virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = 0 ) const = 0; + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const = 0; %Docstring Returns diagram settings for a feature (or ``False`` if the diagram for the feature is not to be rendered). Used internally within :py:func:`~QgsDiagramRenderer.renderDiagram` :param feature: the feature :param c: render context :param s: out: diagram settings for the feature -:param subDiagram: subDiagram object for stacked diagram case %End - virtual QSizeF diagramSize( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagram *subDiagram = 0 ) const = 0; + virtual QSizeF diagramSize( const QgsFeature &feature, const QgsRenderContext &c ) const = 0; %Docstring Returns size of the diagram (in painter units) or an invalid size in case of error :param feature: the feature :param c: render context -:param subDiagram: subDiagram object for stacked diagram case %End void convertSizeToMapUnits( QSizeF &size, const QgsRenderContext &context ) const; @@ -804,13 +804,6 @@ Returns the paint device dpi (or -1 in case of error Reads internal QgsDiagramRenderer state from a DOM element. .. seealso:: _writeXml -%End - - void _readXmlSubdiagrams( const QDomElement &elem, const QgsReadWriteContext &context ); -%Docstring -Reads Stacked Diagram's subdiagram state from a DOM element. - -.. seealso:: _writeXmlSubDiagrams %End void _writeXml( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; @@ -820,12 +813,6 @@ Writes internal QgsDiagramRenderer diagram state to a DOM element. .. seealso:: _readXml %End - void _writeXmlSubDiagrams( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; -%Docstring -Writes Stacked Diagram's subdiagram state to a DOM element. - -.. seealso:: _readXmlSubdiagrams -%End }; @@ -864,10 +851,10 @@ Renders the diagrams for all features with the same settings protected: - virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = 0 ) const; + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const; - virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c, QgsDiagram *subDiagram = 0 ) const; + virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const; }; @@ -957,14 +944,115 @@ Returns configuration of appearance of legend. Will return ``None`` if no config %End protected: - virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = 0 ) const; + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const; + + + virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const; + + +}; + +class QgsStackedDiagramRenderer : QgsDiagramRenderer +{ +%Docstring(signature="appended") +Renders diagrams using mixed diagram render types. The size of +the rendered diagram is given by a combination of subrenderers. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsdiagramrenderer.h" +%End + public: + QgsStackedDiagramRenderer(); + + virtual QgsStackedDiagramRenderer *clone() const /Factory/; + + + virtual QSizeF sizeMapUnits( const QgsFeature &feature, const QgsRenderContext &c ) const; +%Docstring +Returns size of the diagram for a feature in map units. Returns an invalid QSizeF in case of error +%End + + virtual void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; +%Docstring +Renders the diagram for a specified feature at a specific position in the +passed render context, taking all renderers and their own diagrams into account. +Diagram rendering is delegated to renderer's diagram. +%End + + virtual QList diagramSettings() const; + +%Docstring +Returns list with all diagram settings in the renderer +%End + void setDiagramSettings( const QgsDiagramSettings &s ); + + virtual QList diagramAttributes() const; + + + + virtual QString rendererName() const; + + virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context ); + + virtual void writeXml( QDomElement &layerElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; + + + void _readXmlSubRenderers( const QDomElement &elem, const QgsReadWriteContext &context ); +%Docstring +Reads stacked renderers state from a DOM element. + +.. seealso:: _writeXmlSubRenderers +%End - virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c, QgsDiagram *subDiagram = 0 ) const; + void _writeXmlSubRenderers( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; +%Docstring +Writes stacked renderers state to a DOM element. + +.. seealso:: _readXmlSubRenderers +%End + + virtual QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const /Factory/; + + + QList< QgsDiagramRenderer * > renderers() const; +%Docstring +Returns an ordered list with the renderers of the stacked renderer object. +If the stacked diagram orientation is vertical, the list is returned backwards. +%End + + void addRenderer( QgsDiagramRenderer *renderer ); +%Docstring +Adds a renderer to the stacked renderer object. + +:param renderer: diagram renderer to be added to the stacked renderer + Renderers added first will render their diagrams first, i.e., more to + the left (horizontal mode) or more to the top (vertical mode). +%End + + const QgsDiagramRenderer *renderer( const int index ) const; +%Docstring +Returns the renderer at the given ``index``. +@param index index of the disired renderer in the stacked renderer +%End + + int rendererCount() const; +%Docstring +Returns the number of renderers that this stacked renderer is composed of. +%End + + protected: + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const; + + virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const; }; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/src/core/diagram/qgsstackeddiagram.cpp b/src/core/diagram/qgsstackeddiagram.cpp index a1e0596d4865..0c74921c004c 100644 --- a/src/core/diagram/qgsstackeddiagram.cpp +++ b/src/core/diagram/qgsstackeddiagram.cpp @@ -33,70 +33,6 @@ QgsStackedDiagram *QgsStackedDiagram::clone() const return new QgsStackedDiagram( *this ); } -void QgsStackedDiagram::addSubDiagram( QgsDiagram *diagram, QgsDiagramSettings *s ) -{ - mSubDiagrams.append( DiagramData{diagram, s} ); -} - -int QgsStackedDiagram::subDiagramCount() const -{ - return mSubDiagrams.count(); -} - -QString QgsStackedDiagram::subDiagramType( int index ) const -{ - if ( index >= 0 && index < mSubDiagrams.count() ) - { - return mSubDiagrams.at( index ).diagram->diagramName(); - } - return QString(); -} - -QList< QgsDiagram * > QgsStackedDiagram::subDiagrams( const QgsDiagramSettings &s ) const -{ - QList< QgsDiagram * > diagrams; - - if ( s.stackedDiagramMode == QgsDiagramSettings::Horizontal ) - { - for ( const auto &item : std::as_const( mSubDiagrams ) ) - { - diagrams.append( item.diagram ); - } - } - else - { - // We'll draw vertical diagrams backwards, - // so we return the subdiagrams in reverse order - QList< DiagramData >::const_reverse_iterator iter = mSubDiagrams.rbegin(); - for ( ; iter != mSubDiagrams.rend(); ++iter ) - { - diagrams.append( iter->diagram ); - } - } - return diagrams; -} - -QgsDiagramSettings *QgsStackedDiagram::subDiagramSettings( int index ) const -{ - if ( index >= 0 && index < mSubDiagrams.count() ) - { - return mSubDiagrams.at( index ).settings; - } - return nullptr; -} - -QgsDiagramSettings *QgsStackedDiagram::subDiagramSettings( const QgsDiagram *diagram ) const -{ - for ( const auto &item : std::as_const( mSubDiagrams ) ) - { - if ( item.diagram == diagram ) - { - return item.settings; - } - } - return nullptr; -} - void QgsStackedDiagram::subDiagramPosition( QPointF &newPos, const QgsRenderContext &c, const QgsDiagramSettings &s, const QgsDiagramSettings &subSettings ) { QSizeF size = sizePainterUnits( subSettings.size, subSettings, c ); @@ -114,72 +50,19 @@ void QgsStackedDiagram::subDiagramPosition( QPointF &newPos, const QgsRenderCont QSizeF QgsStackedDiagram::diagramSize( const QgsFeature &feature, const QgsRenderContext &c, const QgsDiagramSettings &s, const QgsDiagramInterpolationSettings &is ) { - QSizeF size( 0, 0 ); - if ( feature.attributeCount() == 0 || mSubDiagrams.length() == 0 ) - { - return size; //zero size if no attributes or no subdiagrams - } - - // Iterate subdiagramas and sum their individual sizes - for ( const auto &item : std::as_const( mSubDiagrams ) ) - { - //size += item.diagram->diagramSize( feature, c, *item.settings, is ); - const QSizeF subSize = item.diagram->diagramSize( feature, c, *item.settings, is ); - switch ( s.stackedDiagramMode ) - { - case QgsDiagramSettings::Horizontal: - size.setWidth( size.width() + subSize.width() ); - size.setHeight( subSize.height() ); - break; - - case QgsDiagramSettings::Vertical: - size.setWidth( subSize.width() ); - size.setHeight( size.height() + subSize.height() ); - break; - } - } - - // eh - this method returns size in unknown units ...! We'll have to fake it and use a rough estimation of - // a conversion factor to painter units... - // TODO QGIS 4.0 -- these methods should all use painter units, dependent on the render context scaling... - double painterUnitConversionScale = c.convertToPainterUnits( 1, s.sizeType ); - - const double spacing = c.convertToPainterUnits( s.stackedDiagramSpacing(), s.stackedDiagramSpacingUnit(), s.stackedDiagramSpacingMapUnitScale() ) / painterUnitConversionScale; - - switch ( s.stackedDiagramMode ) - { - case QgsDiagramSettings::Horizontal: - size.scale( size.width() + spacing * ( mSubDiagrams.length() - 1 ), size.height(), Qt::IgnoreAspectRatio ); - break; - - case QgsDiagramSettings::Vertical: - size.scale( size.width(), size.height() + spacing * ( mSubDiagrams.length() - 1 ), Qt::IgnoreAspectRatio ); - break; - } - - if ( s.showAxis() && s.axisLineSymbol() ) - { - const double maxBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( s.axisLineSymbol(), c ) / painterUnitConversionScale; - size.setWidth( size.width() + 2 * maxBleed ); - size.setHeight( size.height() + 2 * maxBleed ); - } - - return size; + Q_UNUSED( feature ) + Q_UNUSED( c ) + Q_UNUSED( s ) + Q_UNUSED( is ) + return QSize( 0, 0 ); } double QgsStackedDiagram::legendSize( double value, const QgsDiagramSettings &s, const QgsDiagramInterpolationSettings &is ) const { - if ( qgsDoubleNear( is.upperValue, is.lowerValue ) ) - return s.minimumSize; // invalid value range => zero size - - // Scale, if extension is smaller than the specified minimum - if ( value < s.minimumSize ) - { - value = s.minimumSize; - } - - double scaleFactor = ( ( is.upperSize.width() - is.lowerSize.width() ) / ( is.upperValue - is.lowerValue ) ); - return value * scaleFactor; + Q_UNUSED( value ) + Q_UNUSED( s ) + Q_UNUSED( is ) + return 0; } QString QgsStackedDiagram::diagramName() const @@ -189,58 +72,10 @@ QString QgsStackedDiagram::diagramName() const QSizeF QgsStackedDiagram::diagramSize( const QgsAttributes &attributes, const QgsRenderContext &c, const QgsDiagramSettings &s ) { - QSizeF size( 0, 0 ); - if ( attributes.isEmpty() || mSubDiagrams.length() == 0 ) - { - return size; //zero size if no attributes or no subdiagrams - } - - // Iterate subdiagramas and sum their individual sizes - // accounting for stacked diagram defined spacing - for ( const auto &item : std::as_const( mSubDiagrams ) ) - { - //size += item.diagram->diagramSize( attributes, c, *item.settings ); - const QSizeF subSize = item.diagram->diagramSize( attributes, c, *item.settings ); - switch ( s.stackedDiagramMode ) - { - case QgsDiagramSettings::Horizontal: - size.setWidth( size.width() + subSize.width() ); - size.setHeight( subSize.height() ); - break; - - case QgsDiagramSettings::Vertical: - size.setWidth( subSize.width() ); - size.setHeight( size.height() + subSize.height() ); - break; - } - } - - // eh - this method returns size in unknown units ...! We'll have to fake it and use a rough estimation of - // a conversion factor to painter units... - // TODO QGIS 4.0 -- these methods should all use painter units, dependent on the render context scaling... - double painterUnitConversionScale = c.convertToPainterUnits( 1, s.sizeType ); - - const double spacing = c.convertToPainterUnits( s.stackedDiagramSpacing(), s.stackedDiagramSpacingUnit(), s.stackedDiagramSpacingMapUnitScale() ) / painterUnitConversionScale; - - switch ( s.stackedDiagramMode ) - { - case QgsDiagramSettings::Horizontal: - size.scale( size.width() + spacing * ( mSubDiagrams.length() - 1 ), size.height(), Qt::IgnoreAspectRatio ); - break; - - case QgsDiagramSettings::Vertical: - size.scale( size.width(), size.height() + spacing * ( mSubDiagrams.length() - 1 ), Qt::IgnoreAspectRatio ); - break; - } - - if ( s.showAxis() && s.axisLineSymbol() ) - { - const double maxBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( s.axisLineSymbol(), c ) / painterUnitConversionScale; - size.setWidth( size.width() + 2 * maxBleed ); - size.setHeight( size.height() + 2 * maxBleed ); - } - - return size; + Q_UNUSED( attributes ) + Q_UNUSED( c ) + Q_UNUSED( s ) + return QSizeF( 0, 0 ); } void QgsStackedDiagram::renderDiagram( const QgsFeature &feature, QgsRenderContext &c, const QgsDiagramSettings &s, QPointF position ) diff --git a/src/core/diagram/qgsstackeddiagram.h b/src/core/diagram/qgsstackeddiagram.h index a8fe74be4973..97f3e3cc1963 100644 --- a/src/core/diagram/qgsstackeddiagram.h +++ b/src/core/diagram/qgsstackeddiagram.h @@ -41,48 +41,10 @@ class CORE_EXPORT QgsStackedDiagram : public QgsDiagram SIP_NODEFAULTCTORS { public: - struct DiagramData - { - QgsDiagram *diagram = nullptr; - QgsDiagramSettings *settings = nullptr; - }; - QgsStackedDiagram(); QgsStackedDiagram *clone() const override SIP_FACTORY; - /** - * Adds a subdiagram to the stacked diagram object along with its corresponding settings. - * \param diagram subdiagram to be added to the stacked diagram - * \param s subdiagram settings - * Subdiagrams added first will appear more to the left (if stacked diagram is horizontal), - * or more to the top (if stacked diagram is vertical). - */ - void addSubDiagram( QgsDiagram *diagram, QgsDiagramSettings *s ); - - /** - * Returns the number of subdiagrams that this stacked diagram is composed of. - */ - int subDiagramCount() const; - - /** - * Returns the type of the subdiagram located at a given \a index. - */ - QString subDiagramType( int index ) const; - - /** - * Returns an ordered list with the subdiagrams of the stacked diagram object. - * If the stacked diagram orientation is vertical, the list is returned backwards. - * \param s stacked diagram settings - */ - QList< QgsDiagram * > subDiagrams( const QgsDiagramSettings &s ) const; - - //! Returns the settings associated to the \a diagram. - QgsDiagramSettings *subDiagramSettings( const QgsDiagram *diagram ) const; - - //! Returns the diagram settings for the diagram located at a given \a index. - QgsDiagramSettings *subDiagramSettings( int index ) const; - /** * Calculates the position for the next subdiagram, updating the \a newPos object. * \param newPos out: position of the previous diagram @@ -103,7 +65,6 @@ class CORE_EXPORT QgsStackedDiagram : public QgsDiagram SIP_NODEFAULTCTORS QBrush mCategoryBrush; QPen mPen; double mScaleFactor; - QList< DiagramData > mSubDiagrams; }; #endif // QGSSTACKEDDIAGRAM_H diff --git a/src/core/qgsdiagramrenderer.cpp b/src/core/qgsdiagramrenderer.cpp index deca426696f5..fa9a8cbe1c92 100644 --- a/src/core/qgsdiagramrenderer.cpp +++ b/src/core/qgsdiagramrenderer.cpp @@ -503,67 +503,26 @@ void QgsDiagramRenderer::renderDiagram( const QgsFeature &feature, QgsRenderCont return; } - if ( mDiagram->diagramName() == QStringLiteral( "Stacked" ) ) + if ( properties.hasActiveProperties() ) { - // Iterate subdiagrams and render them individually - QgsStackedDiagram *stackedDiagram = qgis::down_cast< QgsStackedDiagram *>( mDiagram.get() ); - QList< QgsDiagram * > subDiagrams = stackedDiagram->subDiagrams( s ); - QPointF newPos = pos; // Each subdiagram will have its own newPos - - for ( const auto &subDiagram : std::as_const( subDiagrams ) ) - { - QgsDiagramSettings subSettings; - if ( !diagramSettings( feature, c, subSettings, subDiagram ) ) - { - continue; - } - - if ( properties.hasActiveProperties() ) - { - c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( subSettings.backgroundColor ) ); - subSettings.backgroundColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::BackgroundColor, c.expressionContext(), subSettings.backgroundColor ); - c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( subSettings.penColor ) ); - subSettings.penColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::StrokeColor, c.expressionContext(), subSettings.penColor ); - c.expressionContext().setOriginalValueVariable( subSettings.penWidth ); - subSettings.penWidth = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StrokeWidth, c.expressionContext(), subSettings.penWidth ); - c.expressionContext().setOriginalValueVariable( subSettings.rotationOffset ); - subSettings.rotationOffset = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StartAngle, c.expressionContext(), subSettings.rotationOffset ); - } - - QgsPaintEffect *effect = subSettings.paintEffect(); - std::unique_ptr< QgsEffectPainter > effectPainter; - if ( effect && effect->enabled() ) - { - effectPainter = std::make_unique< QgsEffectPainter >( c, effect ); - } - - subDiagram->renderDiagram( feature, c, subSettings, newPos ); - stackedDiagram->subDiagramPosition( newPos, c, s, subSettings ); - } + c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( s.backgroundColor ) ); + s.backgroundColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::BackgroundColor, c.expressionContext(), s.backgroundColor ); + c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( s.penColor ) ); + s.penColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::StrokeColor, c.expressionContext(), s.penColor ); + c.expressionContext().setOriginalValueVariable( s.penWidth ); + s.penWidth = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StrokeWidth, c.expressionContext(), s.penWidth ); + c.expressionContext().setOriginalValueVariable( s.rotationOffset ); + s.rotationOffset = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StartAngle, c.expressionContext(), s.rotationOffset ); } - else - { - if ( properties.hasActiveProperties() ) - { - c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( s.backgroundColor ) ); - s.backgroundColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::BackgroundColor, c.expressionContext(), s.backgroundColor ); - c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( s.penColor ) ); - s.penColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::StrokeColor, c.expressionContext(), s.penColor ); - c.expressionContext().setOriginalValueVariable( s.penWidth ); - s.penWidth = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StrokeWidth, c.expressionContext(), s.penWidth ); - c.expressionContext().setOriginalValueVariable( s.rotationOffset ); - s.rotationOffset = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StartAngle, c.expressionContext(), s.rotationOffset ); - } - QgsPaintEffect *effect = s.paintEffect(); - std::unique_ptr< QgsEffectPainter > effectPainter; - if ( effect && effect->enabled() ) - { - effectPainter = std::make_unique< QgsEffectPainter >( c, effect ); - } - - mDiagram->renderDiagram( feature, c, s, pos ); + QgsPaintEffect *effect = s.paintEffect(); + std::unique_ptr< QgsEffectPainter > effectPainter; + if ( effect && effect->enabled() ) + { + effectPainter = std::make_unique< QgsEffectPainter >( c, effect ); } + + mDiagram->renderDiagram( feature, c, s, pos ); } QSizeF QgsDiagramRenderer::sizeMapUnits( const QgsFeature &feature, const QgsRenderContext &c ) const @@ -652,7 +611,7 @@ void QgsDiagramRenderer::_readXml( const QDomElement &elem, const QgsReadWriteCo } else if ( diagramType == QLatin1String( "Stacked" ) ) { - _readXmlSubdiagrams( elem, context ); + mDiagram.reset( new QgsStackedDiagram() ); } else { @@ -662,67 +621,6 @@ void QgsDiagramRenderer::_readXml( const QDomElement &elem, const QgsReadWriteCo mShowAttributeLegend = ( elem.attribute( QStringLiteral( "attributeLegend" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) ); } -void QgsDiagramRenderer::_readXmlSubdiagrams( const QDomElement &elem, const QgsReadWriteContext &context ) -{ - Q_UNUSED( context ) - const QDomElement subdiagramsElem = elem.firstChildElement( QStringLiteral( "Subdiagrams" ) ); - - if ( !subdiagramsElem.isNull() ) - { - const QDomNodeList subdiagrams = elem.elementsByTagName( QStringLiteral( "Subdiagram" ) ); - - if ( subdiagrams.length() > 0 ) - { - std::unique_ptr< QgsStackedDiagram > stackedDiagram = std::make_unique< QgsStackedDiagram >(); - - for ( int i = 0; i < subdiagrams.size(); i++ ) - { - const QDomElement subdiagramElem = subdiagrams.at( i ).toElement(); - const QString diagramType = subdiagramElem.attribute( QStringLiteral( "diagramType" ) ); - const QDomElement categoryElem = subdiagramElem.firstChildElement( QStringLiteral( "DiagramCategory" ) ); - - if ( !categoryElem.isNull() ) - { - std::unique_ptr< QgsDiagram > diagram; - std::unique_ptr< QgsDiagramSettings > ds = std::make_unique< QgsDiagramSettings >(); - ds->readXml( categoryElem, context ); - - if ( diagramType == QLatin1String( "Pie" ) ) - { - diagram = std::make_unique< QgsPieDiagram >(); - } - else if ( diagramType == QLatin1String( "Text" ) ) - { - diagram = std::make_unique< QgsTextDiagram >(); - } - else if ( diagramType == QLatin1String( "Histogram" ) ) - { - diagram = std::make_unique< QgsHistogramDiagram >(); - } - else if ( diagramType == QLatin1String( "StackedBar" ) ) - { - diagram = std::make_unique< QgsStackedBarDiagram >(); - } - - if ( diagram ) - { - stackedDiagram->addSubDiagram( diagram.release(), ds.release() ); - } - } - } - - if ( stackedDiagram->subDiagramCount() > 1 ) - { - mDiagram.reset( stackedDiagram.release() ); - return; - } - } - } - - // Fallback - mDiagram.reset( new QgsStackedDiagram() ); -} - void QgsDiagramRenderer::_writeXml( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const { Q_UNUSED( doc ) @@ -735,60 +633,21 @@ void QgsDiagramRenderer::_writeXml( QDomElement &rendererElem, QDomDocument &doc rendererElem.setAttribute( QStringLiteral( "attributeLegend" ), mShowAttributeLegend ); } -void QgsDiagramRenderer::_writeXmlSubDiagrams( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const -{ - QDomElement subDiagramsElem = doc.createElement( QStringLiteral( "Subdiagrams" ) ); - - // Iterate subdiagrams and write their settings to a DOM object - const QgsStackedDiagram *stackedDiagram = qgis::down_cast< const QgsStackedDiagram *>( mDiagram.get() ); - - for ( int i = 0; i < stackedDiagram->subDiagramCount(); i++ ) - { - QDomElement subDiagramElem = doc.createElement( QStringLiteral( "Subdiagram" ) ); - subDiagramElem.setAttribute( QStringLiteral( "diagramType" ), stackedDiagram->subDiagramType( i ) ); - const QgsDiagramSettings *subSettings = stackedDiagram->subDiagramSettings( i ); - subSettings->writeXml( subDiagramElem, doc, context ); - subDiagramsElem.appendChild( subDiagramElem ); - } - rendererElem.appendChild( subDiagramsElem ); -} - QgsSingleCategoryDiagramRenderer *QgsSingleCategoryDiagramRenderer::clone() const { return new QgsSingleCategoryDiagramRenderer( *this ); } -bool QgsSingleCategoryDiagramRenderer::diagramSettings( const QgsFeature &, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram ) const +bool QgsSingleCategoryDiagramRenderer::diagramSettings( const QgsFeature &, const QgsRenderContext &c, QgsDiagramSettings &s ) const { Q_UNUSED( c ) - if ( subDiagram ) - { - // Stacked diagram case, ask the stacked - // diagram object for its subdiagram settings - QgsStackedDiagram *stackedDiagram = qgis::down_cast< QgsStackedDiagram * >( mDiagram.get() ); - s = *stackedDiagram->subDiagramSettings( subDiagram ); - } - else - { - s = mSettings; - } + s = mSettings; return true; } -QSizeF QgsSingleCategoryDiagramRenderer::diagramSize( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagram *subDiagram ) const +QSizeF QgsSingleCategoryDiagramRenderer::diagramSize( const QgsFeature &feature, const QgsRenderContext &c ) const { - if ( subDiagram ) - { - // Stacked diagram case, ask the stacked - // diagram object for its diagram settings - QgsStackedDiagram *stackedDiagram = qgis::down_cast< QgsStackedDiagram * >( mDiagram.get() ); - QgsDiagramSettings subDiagramSettings = *stackedDiagram->subDiagramSettings( subDiagram ); - return subDiagram->diagramSize( feature.attributes(), c, subDiagramSettings ); - } - else - { - return mDiagram->diagramSize( feature.attributes(), c, mSettings ); - } + return mDiagram->diagramSize( feature.attributes(), c, mSettings ); } QList QgsSingleCategoryDiagramRenderer::diagramSettings() const @@ -815,12 +674,6 @@ void QgsSingleCategoryDiagramRenderer::writeXml( QDomElement &layerElem, QDomDoc QDomElement rendererElem = doc.createElement( QStringLiteral( "SingleCategoryDiagramRenderer" ) ); mSettings.writeXml( rendererElem, doc, context ); _writeXml( rendererElem, doc, context ); - - if ( mDiagram->diagramName() == QStringLiteral( "Stacked" ) ) - { - _writeXmlSubDiagrams( rendererElem, doc, context ); - } - layerElem.appendChild( rendererElem ); } @@ -867,19 +720,10 @@ QList QgsLinearlyInterpolatedDiagramRenderer::diagramSetting return settingsList; } -bool QgsLinearlyInterpolatedDiagramRenderer::diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram ) const +bool QgsLinearlyInterpolatedDiagramRenderer::diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const { - if ( subDiagram ) - { - QgsStackedDiagram *stackedDiagram = qgis::down_cast< QgsStackedDiagram * >( mDiagram.get() ); - s = *stackedDiagram->subDiagramSettings( subDiagram ); - s.size = diagramSize( feature, c, subDiagram ); - } - else - { - s = mSettings; - s.size = diagramSize( feature, c ); - } + s = mSettings; + s.size = diagramSize( feature, c ); return true; } @@ -907,20 +751,9 @@ QSet QgsLinearlyInterpolatedDiagramRenderer::referencedFields( const Qg return referenced; } -QSizeF QgsLinearlyInterpolatedDiagramRenderer::diagramSize( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagram *subDiagram ) const +QSizeF QgsLinearlyInterpolatedDiagramRenderer::diagramSize( const QgsFeature &feature, const QgsRenderContext &c ) const { - if ( subDiagram ) - { - // Stacked diagram case, ask the stacked - // diagram object for its diagram settings - QgsStackedDiagram *stackedDiagram = qgis::down_cast< QgsStackedDiagram * >( mDiagram.get() ); - QgsDiagramSettings subDiagramSettings = *stackedDiagram->subDiagramSettings( subDiagram ); - return subDiagram->diagramSize( feature, c, subDiagramSettings, mInterpolationSettings ); - } - else - { - return mDiagram->diagramSize( feature, c, mSettings, mInterpolationSettings ); - } + return mDiagram->diagramSize( feature, c, mSettings, mInterpolationSettings ); } void QgsLinearlyInterpolatedDiagramRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context ) @@ -1001,15 +834,258 @@ void QgsLinearlyInterpolatedDiagramRenderer::writeXml( QDomElement &layerElem, Q } _writeXml( rendererElem, doc, context ); + layerElem.appendChild( rendererElem ); +} + +QgsStackedDiagramRenderer *QgsStackedDiagramRenderer::clone() const +{ + return new QgsStackedDiagramRenderer( *this ); +} + +QSizeF QgsStackedDiagramRenderer::sizeMapUnits( const QgsFeature &feature, const QgsRenderContext &c ) const +{ + QSizeF stackedSize( 0, 0 ); + + // Iterate renderers. For each renderer, get the diagram + // size for the feature and add it to the total size + // accounting for stacked diagram defined spacing + for ( int i = 0; i < mDiagramRenderers.count(); i++ ) + { + QSizeF size = mDiagramRenderers.at( i )->sizeMapUnits( feature, c ); + + if ( size.isValid() ) + { + switch ( mSettings.stackedDiagramMode ) + { + case QgsDiagramSettings::Horizontal: + stackedSize.setWidth( stackedSize.width() + size.width() ); + stackedSize.setHeight( std::max( stackedSize.height(), size.height() ) ); + break; + + case QgsDiagramSettings::Vertical: + stackedSize.setWidth( std::max( stackedSize.width(), size.width() ) ); + stackedSize.setHeight( stackedSize.height() + size.height() ); + break; + } + } + } + + const double spacing = c.convertToMapUnits( mSettings.stackedDiagramSpacing(), mSettings.stackedDiagramSpacingUnit(), mSettings.stackedDiagramSpacingMapUnitScale() ); + + switch ( mSettings.stackedDiagramMode ) + { + case QgsDiagramSettings::Horizontal: + stackedSize.scale( stackedSize.width() + spacing * ( mDiagramRenderers.count() - 1 ), stackedSize.height(), Qt::IgnoreAspectRatio ); + break; + + case QgsDiagramSettings::Vertical: + stackedSize.scale( stackedSize.width(), stackedSize.height() + spacing * ( mDiagramRenderers.count() - 1 ), Qt::IgnoreAspectRatio ); + break; + } + return stackedSize; +} + +void QgsStackedDiagramRenderer::renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties ) const +{ + if ( !mDiagram ) + { + return; + } + + QPointF newPos = pos; // Each subdiagram will have its own newPos + QList< QgsDiagramRenderer * > stackedRenderers = renderers(); + for ( const auto &stackedRenderer : std::as_const( stackedRenderers ) ) + { + if ( stackedRenderer->rendererName() == QStringLiteral( "Stacked" ) ) + { + stackedRenderer->renderDiagram( feature, c, newPos, properties ); + continue; + } + + QgsDiagramSettings s; + if ( !stackedRenderer->diagramSettings( feature, c, s ) ) + { + return; + } - if ( mDiagram->diagramName() == QStringLiteral( "Stacked" ) ) + if ( properties.hasActiveProperties() ) + { + c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( s.backgroundColor ) ); + s.backgroundColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::BackgroundColor, c.expressionContext(), s.backgroundColor ); + c.expressionContext().setOriginalValueVariable( QgsColorUtils::colorToString( s.penColor ) ); + s.penColor = properties.valueAsColor( QgsDiagramLayerSettings::Property::StrokeColor, c.expressionContext(), s.penColor ); + c.expressionContext().setOriginalValueVariable( s.penWidth ); + s.penWidth = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StrokeWidth, c.expressionContext(), s.penWidth ); + c.expressionContext().setOriginalValueVariable( s.rotationOffset ); + s.rotationOffset = properties.valueAsDouble( QgsDiagramLayerSettings::Property::StartAngle, c.expressionContext(), s.rotationOffset ); + } + + QgsPaintEffect *effect = s.paintEffect(); + std::unique_ptr< QgsEffectPainter > effectPainter; + if ( effect && effect->enabled() ) + { + effectPainter = std::make_unique< QgsEffectPainter >( c, effect ); + } + + stackedRenderer->diagram()->renderDiagram( feature, c, s, newPos ); + QgsStackedDiagram *stackedDiagram = dynamic_cast< QgsStackedDiagram *>( mDiagram.get() ); + stackedDiagram->subDiagramPosition( newPos, c, mSettings, s ); + } +} + +bool QgsStackedDiagramRenderer::diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const +{ + Q_UNUSED( feature ) + Q_UNUSED( c ) + Q_UNUSED( s ) + return false; +} + +QSizeF QgsStackedDiagramRenderer::diagramSize( const QgsFeature &feature, const QgsRenderContext &c ) const +{ + Q_UNUSED( feature ) + Q_UNUSED( c ) + return QSizeF( 0, 0 ); +} + +QList QgsStackedDiagramRenderer::diagramSettings() const +{ + QList settingsList; + settingsList.push_back( mSettings ); + return settingsList; +} + +QList QgsStackedDiagramRenderer::diagramAttributes() const +{ + return mSettings.categoryAttributes; +} + +QList< QgsLayerTreeModelLegendNode * > QgsStackedDiagramRenderer::legendItems( QgsLayerTreeLayer *nodeLayer ) const +{ + QList< QgsLayerTreeModelLegendNode * > nodes; + for ( int i = 0; i < rendererCount(); i++ ) { - _writeXmlSubDiagrams( rendererElem, doc, context ); + nodes << mDiagramRenderers.at( i )->legendItems( nodeLayer ); } + return nodes; +} + +QList< QgsDiagramRenderer * > QgsStackedDiagramRenderer::renderers() const +{ + QList< QgsDiagramRenderer * > renderers; + + if ( mSettings.stackedDiagramMode == QgsDiagramSettings::Horizontal ) + { + for ( const auto &item : std::as_const( mDiagramRenderers ) ) + { + renderers.append( item ); + } + } + else + { + // We draw vertical diagrams backwards, so + // we return the subdiagrams in reverse order + QList< QgsDiagramRenderer * >::const_reverse_iterator iter = mDiagramRenderers.rbegin(); + for ( ; iter != mDiagramRenderers.rend(); ++iter ) + { + renderers.append( *iter ); + } + } + return renderers; +} + +void QgsStackedDiagramRenderer::addRenderer( QgsDiagramRenderer *renderer ) +{ + if ( renderer ) + { + mDiagramRenderers.append( renderer ); + } +} + +const QgsDiagramRenderer *QgsStackedDiagramRenderer::renderer( const int index ) const +{ + if ( index >= 0 && index < mDiagramRenderers.count() ) + { + return mDiagramRenderers.at( index ); + } + + return nullptr; +} + +int QgsStackedDiagramRenderer::rendererCount() const +{ + return mDiagramRenderers.count(); +} + +void QgsStackedDiagramRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context ) +{ + const QDomElement categoryElem = elem.firstChildElement( QStringLiteral( "DiagramCategory" ) ); + if ( categoryElem.isNull() ) + { + return; + } + + mSettings.readXml( categoryElem, context ); + _readXml( elem, context ); + _readXmlSubRenderers( elem, context ); +} + +void QgsStackedDiagramRenderer::_readXmlSubRenderers( const QDomElement &elem, const QgsReadWriteContext &context ) +{ + const QDomElement subRenderersElem = elem.firstChildElement( QStringLiteral( "DiagramRenderers" ) ); + + if ( !subRenderersElem.isNull() ) + { + const QDomNodeList childRendererList = subRenderersElem.childNodes(); + + for ( int i = 0; i < childRendererList.size(); i++ ) + { + const QDomElement subRendererElem = childRendererList.at( i ).toElement(); + + if ( subRendererElem.nodeName() == QStringLiteral( "SingleCategoryDiagramRenderer" ) ) + { + std::unique_ptr< QgsSingleCategoryDiagramRenderer > singleCatDiagramRenderer = std::make_unique< QgsSingleCategoryDiagramRenderer >(); + singleCatDiagramRenderer->readXml( subRendererElem, context ); + addRenderer( singleCatDiagramRenderer.release() ); + } + else if ( subRendererElem.nodeName() == QStringLiteral( "LinearlyInterpolatedDiagramRenderer" ) ) + { + std::unique_ptr< QgsLinearlyInterpolatedDiagramRenderer > linearDiagramRenderer = std::make_unique< QgsLinearlyInterpolatedDiagramRenderer >(); + linearDiagramRenderer->readXml( subRendererElem, context ); + addRenderer( linearDiagramRenderer.release() ); + } + else if ( subRendererElem.nodeName() == QStringLiteral( "StackedDiagramRenderer" ) ) + { + std::unique_ptr< QgsStackedDiagramRenderer > stackedDiagramRenderer = std::make_unique< QgsStackedDiagramRenderer >(); + stackedDiagramRenderer->readXml( subRendererElem, context ); + addRenderer( stackedDiagramRenderer.release() ); + } + } + } +} + +void QgsStackedDiagramRenderer::writeXml( QDomElement &layerElem, QDomDocument &doc, const QgsReadWriteContext &context ) const +{ + QDomElement rendererElem = doc.createElement( QStringLiteral( "StackedDiagramRenderer" ) ); + mSettings.writeXml( rendererElem, doc, context ); + _writeXml( rendererElem, doc, context ); + _writeXmlSubRenderers( rendererElem, doc, context ); layerElem.appendChild( rendererElem ); } +void QgsStackedDiagramRenderer::_writeXmlSubRenderers( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const +{ + QDomElement renderersElem = doc.createElement( QStringLiteral( "DiagramRenderers" ) ); + + // Iterate sub renderers and write their settings to a DOM object + for ( int i = 0; i < mDiagramRenderers.count(); i++ ) + { + mDiagramRenderers.at( i )->writeXml( renderersElem, doc, context ); + } + rendererElem.appendChild( renderersElem ); +} + QList< QgsLayerTreeModelLegendNode * > QgsDiagramSettings::legendItems( QgsLayerTreeLayer *nodeLayer ) const { QList< QgsLayerTreeModelLegendNode * > list; diff --git a/src/core/qgsdiagramrenderer.h b/src/core/qgsdiagramrenderer.h index 9741602be1bb..514e43a5dc94 100644 --- a/src/core/qgsdiagramrenderer.h +++ b/src/core/qgsdiagramrenderer.h @@ -44,6 +44,7 @@ class QgsLayerTreeLayer; class QgsPaintEffect; class QgsDataDefinedSizeLegend; class QgsLineSymbol; +class QgsStackedDiagram; namespace pal { class Layer; } SIP_SKIP @@ -744,6 +745,8 @@ class CORE_EXPORT QgsDiagramRenderer sipType = sipType_QgsSingleCategoryDiagramRenderer; else if ( sipCpp->rendererName() == QLatin1String( "LinearlyInterpolated" ) ) sipType = sipType_QgsLinearlyInterpolatedDiagramRenderer; + else if ( sipCpp->rendererName() == QLatin1String( "Stacked" ) ) + sipType = sipType_QgsStackedDiagramRenderer; else sipType = NULL; SIP_END @@ -776,7 +779,7 @@ class CORE_EXPORT QgsDiagramRenderer /** * Renders the diagram for a specified feature at a specific position in the passed render context. */ - void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; + virtual void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const; void setDiagram( QgsDiagram *d SIP_TRANSFER ); QgsDiagram *diagram() const { return mDiagram.get(); } @@ -826,17 +829,15 @@ class CORE_EXPORT QgsDiagramRenderer * \param feature the feature * \param c render context * \param s out: diagram settings for the feature - * \param subDiagram subDiagram object for stacked diagram case */ - virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = nullptr ) const = 0; + virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const = 0; /** * Returns size of the diagram (in painter units) or an invalid size in case of error * \param feature the feature * \param c render context - * \param subDiagram subDiagram object for stacked diagram case */ - virtual QSizeF diagramSize( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagram *subDiagram = nullptr ) const = 0; + virtual QSizeF diagramSize( const QgsFeature &feature, const QgsRenderContext &c ) const = 0; //! Converts size from mm to map units void convertSizeToMapUnits( QSizeF &size, const QgsRenderContext &context ) const; @@ -853,28 +854,18 @@ class CORE_EXPORT QgsDiagramRenderer void _readXml( const QDomElement &elem, const QgsReadWriteContext &context ); /** - * Reads Stacked Diagram's subdiagram state from a DOM element. - * \see _writeXmlSubDiagrams() - */ - void _readXmlSubdiagrams( const QDomElement &elem, const QgsReadWriteContext &context ); - - /** - * Writes internal QgsDiagramRenderer diagram state to a DOM element. - * \see _readXml() - */ + * Writes internal QgsDiagramRenderer diagram state to a DOM element. + * \see _readXml() + */ void _writeXml( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; - /** - * Writes Stacked Diagram's subdiagram state to a DOM element. - * \see _readXmlSubdiagrams() - */ - void _writeXmlSubDiagrams( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; - //! Reference to the object that does the real diagram rendering std::unique_ptr< QgsDiagram > mDiagram; //! Whether to show an attribute legend for the diagrams bool mShowAttributeLegend = true; + + friend class QgsStackedDiagramRenderer; }; /** @@ -903,9 +894,9 @@ class CORE_EXPORT QgsSingleCategoryDiagramRenderer : public QgsDiagramRenderer QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const override SIP_FACTORY; protected: - bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = nullptr ) const override; + bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const override; - QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c, QgsDiagram *subDiagram = nullptr ) const override; + QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const override; private: QgsDiagramSettings mSettings; @@ -984,9 +975,9 @@ class CORE_EXPORT QgsLinearlyInterpolatedDiagramRenderer : public QgsDiagramRend QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const; protected: - bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s, QgsDiagram *subDiagram = nullptr ) const override; + bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const override; - QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c, QgsDiagram *subDiagram = nullptr ) const override; + QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const override; private: QgsDiagramSettings mSettings; @@ -996,4 +987,92 @@ class CORE_EXPORT QgsLinearlyInterpolatedDiagramRenderer : public QgsDiagramRend QgsDataDefinedSizeLegend *mDataDefinedSizeLegend = nullptr; }; +/** + * \ingroup core + * \class QgsStackedDiagramRenderer + * Renders diagrams using mixed diagram render types. The size of + * the rendered diagram is given by a combination of subrenderers. + * + * \since QGIS 3.40 + */ +class CORE_EXPORT QgsStackedDiagramRenderer : public QgsDiagramRenderer +{ + public: + QgsStackedDiagramRenderer() = default; + + QgsStackedDiagramRenderer *clone() const override SIP_FACTORY; + + //! Returns size of the diagram for a feature in map units. Returns an invalid QSizeF in case of error + virtual QSizeF sizeMapUnits( const QgsFeature &feature, const QgsRenderContext &c ) const override; + + /** + * Renders the diagram for a specified feature at a specific position in the + * passed render context, taking all renderers and their own diagrams into account. + * Diagram rendering is delegated to renderer's diagram. + */ + virtual void renderDiagram( const QgsFeature &feature, QgsRenderContext &c, QPointF pos, const QgsPropertyCollection &properties = QgsPropertyCollection() ) const override; + + //! Returns list with all diagram settings in the renderer + QList diagramSettings() const override; + + void setDiagramSettings( const QgsDiagramSettings &s ) { mSettings = s; } + + QList diagramAttributes() const override; + + //QSet< QString > referencedFields( const QgsExpressionContext &context = QgsExpressionContext() ) const override; + + QString rendererName() const override { return QStringLiteral( "Stacked" ); } + + void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override; + void writeXml( QDomElement &layerElem, QDomDocument &doc, const QgsReadWriteContext &context ) const override; + + /** + * Reads stacked renderers state from a DOM element. + * \see _writeXmlSubRenderers() + */ + void _readXmlSubRenderers( const QDomElement &elem, const QgsReadWriteContext &context ); + + /** + * Writes stacked renderers state to a DOM element. + * \see _readXmlSubRenderers() + */ + void _writeXmlSubRenderers( QDomElement &rendererElem, QDomDocument &doc, const QgsReadWriteContext &context ) const; + + QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const override SIP_FACTORY; + + /** + * Returns an ordered list with the renderers of the stacked renderer object. + * If the stacked diagram orientation is vertical, the list is returned backwards. + */ + QList< QgsDiagramRenderer * > renderers() const; + + /** + * Adds a renderer to the stacked renderer object. + * \param renderer diagram renderer to be added to the stacked renderer + * Renderers added first will render their diagrams first, i.e., more to + * the left (horizontal mode) or more to the top (vertical mode). + */ + void addRenderer( QgsDiagramRenderer *renderer ); + + /** + * Returns the renderer at the given \a index. + * @param index index of the disired renderer in the stacked renderer + */ + const QgsDiagramRenderer *renderer( const int index ) const; + + /** + * Returns the number of renderers that this stacked renderer is composed of. + */ + int rendererCount() const; + + protected: + bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const override; + QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const override; + + private: + QgsDiagramSettings mSettings; + QList< QgsDiagramRenderer * > mDiagramRenderers; +}; + + #endif // QGSDIAGRAMRENDERER_H diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index e37ffe6a8483..7810f5273e64 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -2926,6 +2926,12 @@ bool QgsVectorLayer::readStyle( const QDomNode &node, QString &errorMessage, mDiagramRenderer = new QgsLinearlyInterpolatedDiagramRenderer(); mDiagramRenderer->readXml( linearDiagramElem, context ); } + QDomElement stackedDiagramElem = node.firstChildElement( QStringLiteral( "StackedDiagramRenderer" ) ); + if ( !stackedDiagramElem.isNull() ) + { + mDiagramRenderer = new QgsStackedDiagramRenderer(); + mDiagramRenderer->readXml( stackedDiagramElem, context ); + } if ( mDiagramRenderer ) { diff --git a/src/gui/vector/qgsdiagramproperties.cpp b/src/gui/vector/qgsdiagramproperties.cpp index d7f3753940ea..db6613369bd7 100644 --- a/src/gui/vector/qgsdiagramproperties.cpp +++ b/src/gui/vector/qgsdiagramproperties.cpp @@ -270,10 +270,14 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer *layer, QWidget *pare connect( mButtonSizeLegendSettings, &QPushButton::clicked, this, &QgsDiagramProperties::showSizeLegendDialog ); } -void QgsDiagramProperties::syncToLayer() +void QgsDiagramProperties::syncToLayer( const QgsDiagramRenderer *dr ) { mDiagramAttributesTreeWidget->clear(); - const QgsDiagramRenderer *dr = mLayer->diagramRenderer(); + if ( !dr ) + { + dr = mLayer->diagramRenderer(); + } + if ( !dr ) //no diagram renderer yet, insert reasonable default { mFixedSizeRadio->setChecked( true ); @@ -323,8 +327,6 @@ void QgsDiagramProperties::syncToLayer() } else // already a diagram renderer present { - const QgsStackedDiagram *stackedDiagram = nullptr; - //single category renderer or interpolated one? if ( dr->rendererName() == QLatin1String( "SingleCategory" ) ) { @@ -338,48 +340,28 @@ void QgsDiagramProperties::syncToLayer() mLinearScaleFrame->setEnabled( mAttributeBasedScalingRadio->isChecked() ); mCheckBoxAttributeLegend->setChecked( dr->attributeLegend() ); - bool diagramSettingsSet = false; - QgsDiagramSettings diagramSettings; + // Assume single category or linearly interpolated diagram renderer for now. + const QList settingList = dr->diagramSettings(); - if ( dr->diagram() && mSubDiagramIndex >= 0 ) + if ( !settingList.isEmpty() ) { - // The current diagram is a subdiagram of a stacked diagram. - // Get the diagram settings using the subdiagram index. - stackedDiagram = dynamic_cast< QgsStackedDiagram * >( dr->diagram() ); - diagramSettings = *stackedDiagram->subDiagramSettings( mSubDiagramIndex ); - diagramSettingsSet = true; - } - else // Single diagram case - { - // Assume single category or linearly interpolated diagram renderer for now. - const QList settingList = dr->diagramSettings(); - - if ( !settingList.isEmpty() ) - { - diagramSettings = settingList.at( 0 ); - diagramSettingsSet = true; - } - } - - if ( diagramSettingsSet ) - { - mDiagramFrame->setEnabled( diagramSettings.enabled ); - mDiagramFontButton->setCurrentFont( diagramSettings.font ); - const QSizeF size = diagramSettings.size; - mBackgroundColorButton->setColor( diagramSettings.backgroundColor ); - mOpacityWidget->setOpacity( diagramSettings.opacity ); - mDiagramPenColorButton->setColor( diagramSettings.penColor ); - mPenWidthSpinBox->setValue( diagramSettings.penWidth ); + mDiagramFrame->setEnabled( settingList.at( 0 ).enabled ); + mDiagramFontButton->setCurrentFont( settingList.at( 0 ).font ); + const QSizeF size = settingList.at( 0 ).size; + mBackgroundColorButton->setColor( settingList.at( 0 ).backgroundColor ); + mOpacityWidget->setOpacity( settingList.at( 0 ).opacity ); + mDiagramPenColorButton->setColor( settingList.at( 0 ).penColor ); + mPenWidthSpinBox->setValue( settingList.at( 0 ).penWidth ); mDiagramSizeSpinBox->setValue( ( size.width() + size.height() ) / 2.0 ); - mScaleRangeWidget->setScaleRange( ( diagramSettings.minimumScale > 0 ? diagramSettings.minimumScale : mLayer->minimumScale() ), - ( diagramSettings.maximumScale > 0 ? diagramSettings.maximumScale : mLayer->maximumScale() ) ); - mScaleVisibilityGroupBox->setChecked( diagramSettings.scaleBasedVisibility ); - mDiagramUnitComboBox->setUnit( diagramSettings.sizeType ); - mDiagramUnitComboBox->setMapUnitScale( diagramSettings.sizeScale ); - mDiagramLineUnitComboBox->setUnit( diagramSettings.lineSizeUnit ); - mDiagramLineUnitComboBox->setMapUnitScale( diagramSettings.lineSizeScale ); - - if ( diagramSettings.labelPlacementMethod == QgsDiagramSettings::Height ) + mScaleRangeWidget->setScaleRange( ( settingList.at( 0 ).minimumScale > 0 ? settingList.at( 0 ).minimumScale : mLayer->minimumScale() ), + ( settingList.at( 0 ).maximumScale > 0 ? settingList.at( 0 ).maximumScale : mLayer->maximumScale() ) ); + mScaleVisibilityGroupBox->setChecked( settingList.at( 0 ).scaleBasedVisibility ); + mDiagramUnitComboBox->setUnit( settingList.at( 0 ).sizeType ); + mDiagramUnitComboBox->setMapUnitScale( settingList.at( 0 ).sizeScale ); + mDiagramLineUnitComboBox->setUnit( settingList.at( 0 ).lineSizeUnit ); + mDiagramLineUnitComboBox->setMapUnitScale( settingList.at( 0 ).lineSizeScale ); + + if ( settingList.at( 0 ).labelPlacementMethod == QgsDiagramSettings::Height ) { mLabelPlacementComboBox->setCurrentIndex( 0 ); } @@ -388,13 +370,13 @@ void QgsDiagramProperties::syncToLayer() mLabelPlacementComboBox->setCurrentIndex( 1 ); } - if ( diagramSettings.paintEffect() ) - mPaintEffect.reset( diagramSettings.paintEffect()->clone() ); + if ( settingList.at( 0 ).paintEffect() ) + mPaintEffect.reset( settingList.at( 0 ).paintEffect()->clone() ); - mAngleOffsetComboBox->setCurrentIndex( mAngleOffsetComboBox->findData( diagramSettings.rotationOffset ) ); - mAngleDirectionComboBox->setCurrentIndex( mAngleDirectionComboBox->findData( diagramSettings.direction() ) ); + mAngleOffsetComboBox->setCurrentIndex( mAngleOffsetComboBox->findData( settingList.at( 0 ).rotationOffset ) ); + mAngleDirectionComboBox->setCurrentIndex( mAngleDirectionComboBox->findData( settingList.at( 0 ).direction() ) ); - switch ( diagramSettings.diagramOrientation ) + switch ( settingList.at( 0 ).diagramOrientation ) { case QgsDiagramSettings::Left: mOrientationLeftButton->setChecked( true ); @@ -413,22 +395,22 @@ void QgsDiagramProperties::syncToLayer() break; } - mBarWidthSpinBox->setValue( diagramSettings.barWidth ); - mBarSpacingSpinBox->setValue( diagramSettings.spacing() ); - mBarSpacingUnitComboBox->setUnit( diagramSettings.spacingUnit() ); - mBarSpacingUnitComboBox->setMapUnitScale( diagramSettings.spacingMapUnitScale() ); + mBarWidthSpinBox->setValue( settingList.at( 0 ).barWidth ); + mBarSpacingSpinBox->setValue( settingList.at( 0 ).spacing() ); + mBarSpacingUnitComboBox->setUnit( settingList.at( 0 ).spacingUnit() ); + mBarSpacingUnitComboBox->setMapUnitScale( settingList.at( 0 ).spacingMapUnitScale() ); - mShowAxisGroupBox->setChecked( diagramSettings.showAxis() ); - if ( diagramSettings.axisLineSymbol() ) - mAxisLineStyleButton->setSymbol( diagramSettings.axisLineSymbol()->clone() ); + mShowAxisGroupBox->setChecked( settingList.at( 0 ).showAxis() ); + if ( settingList.at( 0 ).axisLineSymbol() ) + mAxisLineStyleButton->setSymbol( settingList.at( 0 ).axisLineSymbol()->clone() ); - mIncreaseSmallDiagramsCheck->setChecked( diagramSettings.minimumSize != 0 ); + mIncreaseSmallDiagramsCheck->setChecked( settingList.at( 0 ).minimumSize != 0 ); mIncreaseMinimumSizeSpinBox->setEnabled( mIncreaseSmallDiagramsCheck->isChecked() ); mIncreaseMinimumSizeLabel->setEnabled( mIncreaseSmallDiagramsCheck->isChecked() ); - mIncreaseMinimumSizeSpinBox->setValue( diagramSettings.minimumSize ); + mIncreaseMinimumSizeSpinBox->setValue( settingList.at( 0 ).minimumSize ); - if ( diagramSettings.scaleByArea ) + if ( settingList.at( 0 ).scaleByArea ) { mScaleDependencyComboBox->setCurrentIndex( 0 ); } @@ -437,9 +419,9 @@ void QgsDiagramProperties::syncToLayer() mScaleDependencyComboBox->setCurrentIndex( 1 ); } - const QList< QColor > categoryColors = diagramSettings.categoryColors; - const QList< QString > categoryAttributes = diagramSettings.categoryAttributes; - const QList< QString > categoryLabels = diagramSettings.categoryLabels; + const QList< QColor > categoryColors = settingList.at( 0 ).categoryColors; + const QList< QString > categoryAttributes = settingList.at( 0 ).categoryAttributes; + const QList< QString > categoryLabels = settingList.at( 0 ).categoryLabels; QList< QString >::const_iterator catIt = categoryAttributes.constBegin(); QList< QColor >::const_iterator coIt = categoryColors.constBegin(); QList< QString >::const_iterator labIt = categoryLabels.constBegin(); @@ -524,17 +506,10 @@ void QgsDiagramProperties::syncToLayer() if ( dr->diagram() ) { - if ( stackedDiagram ) - { - mDiagramType = stackedDiagram->subDiagramType( mSubDiagramIndex ); - } - else - { - mDiagramType = dr->diagram()->diagramName(); - } + mDiagramType = dr->diagram()->diagramName(); mDiagramTypeComboBox->blockSignals( true ); - mDiagramTypeComboBox->setCurrentIndex( ( diagramSettingsSet && diagramSettings.enabled ) ? mDiagramTypeComboBox->findData( mDiagramType ) : 0 ); + mDiagramTypeComboBox->setCurrentIndex( ( settingList.at( 0 ).enabled ) ? mDiagramTypeComboBox->findData( mDiagramType ) : 0 ); mDiagramTypeComboBox->blockSignals( false ); //force a refresh of widget status to match diagram type mDiagramTypeComboBox_currentIndexChanged( mDiagramTypeComboBox->currentIndex() ); diff --git a/src/gui/vector/qgsdiagramproperties.h b/src/gui/vector/qgsdiagramproperties.h index 34b8bb2b606a..fbb4a43d0c89 100644 --- a/src/gui/vector/qgsdiagramproperties.h +++ b/src/gui/vector/qgsdiagramproperties.h @@ -49,7 +49,7 @@ class GUI_EXPORT QgsDiagramProperties : public QWidget, private Ui::QgsDiagramPr * * \since QGIS 3.16 */ - void syncToLayer(); + void syncToLayer( const QgsDiagramRenderer *dr = nullptr ); ~QgsDiagramProperties() override; @@ -83,13 +83,6 @@ class GUI_EXPORT QgsDiagramProperties : public QWidget, private Ui::QgsDiagramPr private: - /** - * Subdiagram position in a stacked diagram. - * If the diagram is not part of a stacked diagram its value is -1. - * Used to access corresponding diagram settings. - */ - int mSubDiagramIndex = -1; - QgsVectorLayer *mLayer = nullptr; //! Point placement button group QButtonGroup *mPlacePointBtnGrp = nullptr; diff --git a/src/gui/vector/qgsstackeddiagramproperties.cpp b/src/gui/vector/qgsstackeddiagramproperties.cpp index 142e867b87c5..8bdac8ff0b3d 100644 --- a/src/gui/vector/qgsstackeddiagramproperties.cpp +++ b/src/gui/vector/qgsstackeddiagramproperties.cpp @@ -101,7 +101,7 @@ void QgsStackedDiagramProperties::syncToLayer() if ( dr && dr->diagram() ) { - if ( dr->diagram()->diagramName() == DIAGRAM_NAME_STACKED ) + if ( dr->rendererName() == QStringLiteral( "Stacked" ) ) { mDiagramTypeComboBox->blockSignals( true ); mDiagramTypeComboBox->setCurrentIndex( mDiagramTypeComboBox->findData( QgsDiagramLayerSettings::Stacked ) ); @@ -114,23 +114,27 @@ void QgsStackedDiagramProperties::syncToLayer() mStackedDiagramSpacingSpinBox->setValue( settingList.at( 0 ).stackedDiagramSpacing() ); mStackedDiagramSpacingUnitComboBox->setUnit( settingList.at( 0 ).stackedDiagramSpacingUnit() ); - // Create as many tabs as necessary - const QgsStackedDiagram *stackedDiagram = dynamic_cast< const QgsStackedDiagram *>( dr->diagram() ); - const int subDiagramCount = stackedDiagram->subDiagramCount(); - while ( mSubDiagramsTabWidget->count() < subDiagramCount ) + // Create/remove as many tabs as necessary + const QgsStackedDiagramRenderer *stackedDiagramRenderer = static_cast< const QgsStackedDiagramRenderer * >( dr ); + const int rendererCount = stackedDiagramRenderer->rendererCount(); + while ( mSubDiagramsTabWidget->count() < rendererCount ) { addSubDiagram(); } + while ( mSubDiagramsTabWidget->count() > rendererCount ) + { + mSubDiagramsTabWidget->setCurrentIndex( mSubDiagramsTabWidget->count() - 1 ); + removeSubDiagram(); + } - // Call subdiagrams' syncToLayer with the corresponding subdiagram index + // Call subdiagrams' syncToLayer with the corresponding rendering object for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) { QgsDiagramProperties *diagramProperties = static_cast( mSubDiagramsTabWidget->widget( i ) ); - diagramProperties->mSubDiagramIndex = i; - diagramProperties->syncToLayer(); + diagramProperties->syncToLayer( stackedDiagramRenderer->renderer( i ) ); } } - else + else // Single diagram { mDiagramTypeComboBox->blockSignals( true ); mDiagramTypeComboBox->setCurrentIndex( mDiagramTypeComboBox->findData( QgsDiagramLayerSettings::Single ) ); @@ -143,10 +147,10 @@ void QgsStackedDiagramProperties::syncToLayer() static_cast( mSubDiagramsTabWidget->widget( 0 ) )->syncToLayer(); } } - else + else // No Diagram { mDiagramTypeComboBox->blockSignals( true ); - mDiagramTypeComboBox->setCurrentIndex( 0 ); // No Diagram + mDiagramTypeComboBox->setCurrentIndex( 0 ); mDiagramTypeComboBox->blockSignals( false ); //force a refresh of widget status to match diagram type mDiagramTypeComboBox_currentIndexChanged( mDiagramTypeComboBox->currentIndex() ); @@ -187,16 +191,15 @@ void QgsStackedDiagramProperties::apply() } else // Stacked diagram { - // TODO: Validate that we have at least 2 diagrams - - // Create DiagramSetings for the StackedDiagram - std::unique_ptr< QgsDiagramSettings> ds = std::make_unique(); + // Create diagram settings for the StackedDiagram + std::unique_ptr< QgsDiagramSettings> ds = std::make_unique< QgsDiagramSettings >(); ds->stackedDiagramMode = static_cast( mStackedDiagramModeComboBox->currentData().toInt() ); ds->setStackedDiagramSpacingUnit( mStackedDiagramSpacingUnitComboBox->unit() ); ds->setStackedDiagramSpacing( mStackedDiagramSpacingSpinBox->value() ); - // Add subdiagrams with their DiagramSettings to StackedDiagram - std::unique_ptr< QgsStackedDiagram > stackedDiagram = std::make_unique< QgsStackedDiagram >(); + // Create diagram renderer for the StackedDiagram + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); // Get DiagramSettings from each subdiagram for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) @@ -207,6 +210,8 @@ void QgsStackedDiagramProperties::apply() ds->categoryLabels += ds1->categoryLabels; ds->categoryColors += ds1->categoryColors; + std::unique_ptr< QgsDiagramRenderer > dr1 = diagramProperties->createRendererBaseInfo( *ds1 ); + std::unique_ptr< QgsDiagram > diagram; if ( diagramProperties->mDiagramType == DIAGRAM_NAME_TEXT ) @@ -225,18 +230,16 @@ void QgsStackedDiagramProperties::apply() { diagram = std::make_unique< QgsHistogramDiagram >(); } - stackedDiagram->addSubDiagram( diagram.release(), ds1.release() ); - } - // Get first diagram to configure some stacked diagram settings from it - QgsDiagramProperties *firstDiagramProperties = static_cast( mSubDiagramsTabWidget->widget( 0 ) ); + dr1->setDiagram( diagram.release() ); + dr->addRenderer( dr1.release() ); + } - // Create DiagramRenderer using info from first diagram and setting Stacked Diagram and the stacked diagram's DiagramSettings - std::unique_ptr< QgsDiagramRenderer > renderer = firstDiagramProperties->createRendererBaseInfo( *ds ); - renderer->setDiagram( stackedDiagram.release() ); - mLayer->setDiagramRenderer( renderer.release() ); + dr->setDiagramSettings( *ds ); + mLayer->setDiagramRenderer( dr ); // Create DiagramLayerSettings from first diagram + QgsDiagramProperties *firstDiagramProperties = static_cast< QgsDiagramProperties * >( mSubDiagramsTabWidget->widget( 0 ) ); QgsDiagramLayerSettings dls = firstDiagramProperties->createDiagramLayerSettings(); mLayer->setDiagramLayerSettings( dls ); diff --git a/src/gui/vector/qgsstackeddiagramproperties.h b/src/gui/vector/qgsstackeddiagramproperties.h index 769cb4c43e4d..8fb3e4d3e868 100644 --- a/src/gui/vector/qgsstackeddiagramproperties.h +++ b/src/gui/vector/qgsstackeddiagramproperties.h @@ -63,8 +63,6 @@ class GUI_EXPORT QgsStackedDiagramProperties : public QWidget, private Ui::QgsSt /** * Adds a diagram tab to the current QgsStackedDiagramProperties. - * - * \since QGIS 3.40 */ void addSubDiagram(); @@ -72,15 +70,12 @@ class GUI_EXPORT QgsStackedDiagramProperties : public QWidget, private Ui::QgsSt * Removes a diagram tab from the current QgsStackedDiagramProperties. * Diagram tabs are removed only if the tab count is greeater than 2. * Tab texts are adjusted after tab removal, to keep sequential order. - * - * \since QGIS 3.40 */ void removeSubDiagram(); private: QgsVectorLayer *mLayer = nullptr; QgsMapCanvas *mMapCanvas = nullptr; - }; #endif // QGSSTACKEDDIAGRAMPROPERTIES_H diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index af590100965a..3ae65bc61dbb 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -135,6 +135,7 @@ "pal::PointSet": ["getCentroid(double &px, double &py, bool forceInside=false) const", "PointSet(double x, double y)", "invalidateGeos() const", "getGeosType() const", "PointSet(int nbPoints, double *x, double *y)", "getNumPoints() const", "deleteCoords()", "preparedGeom() const", "PointSet(const PointSet &ps)", "createGeosGeom() const"], "QgsSimpleFillSymbolLayer": ["createFromSld(QDomElement &element)", "setBrushStyle(Qt::BrushStyle style)", "setStrokeWidth(double strokeWidth)", "strokeWidth() const", "brushStyle() const", "strokeWidthMapUnitScale() const", "strokeStyle() const", "setPenJoinStyle(Qt::PenJoinStyle style)", "QgsSimpleFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, Qt::BrushStyle style=DEFAULT_SIMPLEFILL_STYLE, const QColor &strokeColor=DEFAULT_SIMPLEFILL_BORDERCOLOR, Qt::PenStyle strokeStyle=DEFAULT_SIMPLEFILL_BORDERSTYLE, double strokeWidth=DEFAULT_SIMPLEFILL_BORDERWIDTH, Qt::PenJoinStyle penJoinStyle=DEFAULT_SIMPLEFILL_JOINSTYLE)", "setStrokeWidthMapUnitScale(const QgsMapUnitScale &scale)", "penJoinStyle() const", "setStrokeStyle(Qt::PenStyle strokeStyle)"], "QgsSingleCategoryDiagramRenderer": ["setDiagramSettings(const QgsDiagramSettings &s)"], + "QgsStackedDiagramRenderer": ["setDiagramSettings(const QgsDiagramSettings &s)"], "QgsPointDisplacementRendererWidget": ["QgsPointDisplacementRendererWidget(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)", "create(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)"], "QgsSingleBandGrayRendererWidget": ["create(QgsRasterLayer *layer, const QgsRectangle &extent)", "QgsSingleBandGrayRendererWidget(QgsRasterLayer *layer, const QgsRectangle &extent=QgsRectangle())"], "QgsLayerTreeModel": ["nodeWillRemoveChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)", "disconnectFromLayers(QgsLayerTreeGroup *parentGroup)", "connectToLayers(QgsLayerTreeGroup *parentGroup)", "nodeLayerWillBeUnloaded()", "legendInvalidateMapBasedData()", "nodeCustomPropertyChanged(QgsLayerTreeNode *node, const QString &key)", "legendNodeData(QgsLayerTreeModelLegendNode *node, int role) const", "legendCleanup()", "Flag", "iconGroup()", "disconnectFromRootNode()", "layerNeedsUpdate()", "addLegendToLayer(QgsLayerTreeLayer *nodeL)", "nodeVisibilityChanged(QgsLayerTreeNode *node)", "layerLegendChanged()", "legendRootRowCount(QgsLayerTreeLayer *nL) const", "removeLegendFromLayer(QgsLayerTreeLayer *nodeLayer)", "legendNodeRowCount(QgsLayerTreeModelLegendNode *node) const", "legendNodeDataChanged()", "connectToRootNode()", "legendNodeFlags(QgsLayerTreeModelLegendNode *node) const", "indexOfParentLayerTreeNode(QgsLayerTreeNode *parentNode) const", "nodeLayerLoaded()", "nodeWillAddChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)", "nodeRemovedChildren()", "legendRootIndex(int row, int column, QgsLayerTreeLayer *nL) const", "connectToLayer(QgsLayerTreeLayer *nodeLayer)", "legendIconEmbeddedInParent(QgsLayerTreeLayer *nodeLayer) const", "nodeAddedChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)", "legendNodeIndex(int row, int column, QgsLayerTreeModelLegendNode *node) const", "invalidateLegendMapBasedData()", "disconnectFromLayer(QgsLayerTreeLayer *nodeLayer)", "legendEmbeddedInParent(QgsLayerTreeLayer *nodeLayer) const", "legendParent(QgsLayerTreeModelLegendNode *legendNode) const"], diff --git a/tests/src/core/testqgsstackeddiagram.cpp b/tests/src/core/testqgsstackeddiagram.cpp index c80631949436..f39a14d4dd54 100644 --- a/tests/src/core/testqgsstackeddiagram.cpp +++ b/tests/src/core/testqgsstackeddiagram.cpp @@ -13,17 +13,6 @@ * * ***************************************************************************/ #include "qgstest.h" -#include -#include -#include -#include -#include -#include -#include -#include - -//qgis includes... -// #include #include "diagram/qgspiediagram.h" #include "diagram/qgstextdiagram.h" #include "diagram/qgsstackedbardiagram.h" @@ -35,12 +24,18 @@ #include "qgsapplication.h" #include "qgsrenderer.h" #include "qgssinglesymbolrenderer.h" -//qgis test includes #include "qgsproject.h" -#include "qgsshadoweffect.h" -#include "qgslinesymbol.h" #include "qgsmarkersymbol.h" +#include +#include +#include +#include +#include +#include +#include +#include + /** * \ingroup UnitTests * Unit tests for stacked diagrams @@ -140,6 +135,15 @@ class TestQgsStackedDiagram : public QgsTest ds1.rotationOffset = 0; ds1.diagramOrientation = QgsDiagramSettings::Left; + QgsLinearlyInterpolatedDiagramRenderer *dr1 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr1->setDiagram( new QgsHistogramDiagram() ); + dr1->setDiagramSettings( ds1 ); + dr1->setLowerValue( 0.0 ); + dr1->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr1->setUpperValue( 15000 ); + dr1->setUpperSize( QSizeF( 20, 20 ) ); + //dr1->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"maennlich_6_17\", \"maennlich_unter_6\")" ) ); //#spellok + // Histogram 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -162,24 +166,26 @@ class TestQgsStackedDiagram : public QgsTest ds2.rotationOffset = 0; ds2.diagramOrientation = QgsDiagramSettings::Right; + QgsLinearlyInterpolatedDiagramRenderer *dr2 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr2->setDiagram( new QgsHistogramDiagram() ); + dr2->setDiagramSettings( ds2 ); + dr2->setLowerValue( 0.0 ); + dr2->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr2->setUpperValue( 15000 ); + dr2->setUpperSize( QSizeF( 20, 20 ) ); + //dr2->setClassificationField( QStringLiteral( "max(\"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Horizontal; ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Pixels ); ds.setStackedDiagramSpacing( 0 ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds2 ); - - QgsLinearlyInterpolatedDiagramRenderer *dr = new QgsLinearlyInterpolatedDiagramRenderer(); - dr->setLowerValue( 0.0 ); - dr->setLowerSize( QSizeF( 0.0, 0.0 ) ); - dr->setUpperValue( 15000 ); - dr->setUpperSize( QSizeF( 20, 20 ) ); - dr->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -218,6 +224,15 @@ class TestQgsStackedDiagram : public QgsTest ds1.rotationOffset = 0; ds1.diagramOrientation = QgsDiagramSettings::Up; + QgsLinearlyInterpolatedDiagramRenderer *dr1 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr1->setDiagram( new QgsHistogramDiagram() ); + dr1->setDiagramSettings( ds1 ); + dr1->setLowerValue( 0.0 ); + dr1->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr1->setUpperValue( 15000 ); + dr1->setUpperSize( QSizeF( 20, 20 ) ); + //dr1->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"maennlich_6_17\", \"maennlich_unter_6\")" ) ); //#spellok + // Histogram 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -240,24 +255,26 @@ class TestQgsStackedDiagram : public QgsTest ds2.rotationOffset = 0; ds2.diagramOrientation = QgsDiagramSettings::Down; + QgsLinearlyInterpolatedDiagramRenderer *dr2 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr2->setDiagram( new QgsHistogramDiagram() ); + dr2->setDiagramSettings( ds2 ); + dr2->setLowerValue( 0.0 ); + dr2->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr2->setUpperValue( 15000 ); + dr2->setUpperSize( QSizeF( 20, 20 ) ); + //dr2->setClassificationField( QStringLiteral( "max(\"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Vertical; ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Pixels ); ds.setStackedDiagramSpacing( 0 ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds2 ); - - QgsLinearlyInterpolatedDiagramRenderer *dr = new QgsLinearlyInterpolatedDiagramRenderer(); - dr->setLowerValue( 0.0 ); - dr->setLowerSize( QSizeF( 0.0, 0.0 ) ); - dr->setUpperValue( 15000 ); - dr->setUpperSize( QSizeF( 20, 20 ) ); - dr->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -296,6 +313,15 @@ class TestQgsStackedDiagram : public QgsTest ds1.rotationOffset = 0; ds1.diagramOrientation = QgsDiagramSettings::Up; + QgsLinearlyInterpolatedDiagramRenderer *dr1 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr1->setDiagram( new QgsHistogramDiagram() ); + dr1->setDiagramSettings( ds1 ); + dr1->setLowerValue( 0.0 ); + dr1->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr1->setUpperValue( 15000 ); + dr1->setUpperSize( QSizeF( 20, 20 ) ); + //dr1->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"maennlich_6_17\", \"maennlich_unter_6\")" ) ); //#spellok + // Histogram 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -318,24 +344,26 @@ class TestQgsStackedDiagram : public QgsTest ds2.rotationOffset = 0; ds2.diagramOrientation = QgsDiagramSettings::Down; + QgsLinearlyInterpolatedDiagramRenderer *dr2 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr2->setDiagram( new QgsHistogramDiagram() ); + dr2->setDiagramSettings( ds2 ); + dr2->setLowerValue( 0.0 ); + dr2->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr2->setUpperValue( 15000 ); + dr2->setUpperSize( QSizeF( 20, 20 ) ); + //dr2->setClassificationField( QStringLiteral( "max(\"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Vertical; ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Points ); ds.setStackedDiagramSpacing( 8 ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds2 ); - - QgsLinearlyInterpolatedDiagramRenderer *dr = new QgsLinearlyInterpolatedDiagramRenderer(); - dr->setLowerValue( 0.0 ); - dr->setLowerSize( QSizeF( 0.0, 0.0 ) ); - dr->setUpperValue( 15000 ); - dr->setUpperSize( QSizeF( 20, 20 ) ); - dr->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -376,6 +404,15 @@ class TestQgsStackedDiagram : public QgsTest ds1.setSpacing( 8 ); ds1.setSpacingUnit( Qgis::RenderUnit::Points ); + QgsLinearlyInterpolatedDiagramRenderer *dr1 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr1->setDiagram( new QgsHistogramDiagram() ); + dr1->setDiagramSettings( ds1 ); + dr1->setLowerValue( 0.0 ); + dr1->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr1->setUpperValue( 15000 ); + dr1->setUpperSize( QSizeF( 20, 20 ) ); + //dr1->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"maennlich_6_17\", \"maennlich_unter_6\")" ) ); //#spellok + // Histogram 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -400,24 +437,26 @@ class TestQgsStackedDiagram : public QgsTest ds2.setSpacing( 8 ); ds2.setSpacingUnit( Qgis::RenderUnit::Points ); + QgsLinearlyInterpolatedDiagramRenderer *dr2 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr2->setDiagram( new QgsHistogramDiagram() ); + dr2->setDiagramSettings( ds2 ); + dr2->setLowerValue( 0.0 ); + dr2->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr2->setUpperValue( 15000 ); + dr2->setUpperSize( QSizeF( 20, 20 ) ); + //dr2->setClassificationField( QStringLiteral( "max(\"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Horizontal; ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Points ); ds.setStackedDiagramSpacing( 8 ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds2 ); - - QgsLinearlyInterpolatedDiagramRenderer *dr = new QgsLinearlyInterpolatedDiagramRenderer(); - dr->setLowerValue( 0.0 ); - dr->setLowerSize( QSizeF( 0.0, 0.0 ) ); - dr->setUpperValue( 15000 ); - dr->setUpperSize( QSizeF( 20, 20 ) ); - dr->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -458,6 +497,15 @@ class TestQgsStackedDiagram : public QgsTest ds1.setSpacing( 0 ); ds1.setSpacingUnit( Qgis::RenderUnit::Points ); + QgsLinearlyInterpolatedDiagramRenderer *dr1 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr1->setDiagram( new QgsHistogramDiagram() ); + dr1->setDiagramSettings( ds1 ); + dr1->setLowerValue( 0.0 ); + dr1->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr1->setUpperValue( 15000 ); + dr1->setUpperSize( QSizeF( 20, 20 ) ); + //dr1->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"maennlich_6_17\", \"maennlich_unter_6\")" ) ); //#spellok + // Histogram 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -482,24 +530,26 @@ class TestQgsStackedDiagram : public QgsTest ds2.setSpacing( 0 ); ds2.setSpacingUnit( Qgis::RenderUnit::Points ); + QgsLinearlyInterpolatedDiagramRenderer *dr2 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr2->setDiagram( new QgsHistogramDiagram() ); + dr2->setDiagramSettings( ds2 ); + dr2->setLowerValue( 0.0 ); + dr2->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr2->setUpperValue( 15000 ); + dr2->setUpperSize( QSizeF( 20, 20 ) ); + //dr2->setClassificationField( QStringLiteral( "max(\"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Horizontal; ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Points ); ds.setStackedDiagramSpacing( 8 ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsHistogramDiagram(), &ds2 ); - - QgsLinearlyInterpolatedDiagramRenderer *dr = new QgsLinearlyInterpolatedDiagramRenderer(); - dr->setLowerValue( 0.0 ); - dr->setLowerSize( QSizeF( 0.0, 0.0 ) ); - dr->setUpperValue( 15000 ); - dr->setUpperSize( QSizeF( 20, 20 ) ); - dr->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -630,6 +680,10 @@ class TestQgsStackedDiagram : public QgsTest ds1.rotationOffset = 270; ds1.setDirection( QgsDiagramSettings::Counterclockwise ); + QgsSingleCategoryDiagramRenderer *dr1 = new QgsSingleCategoryDiagramRenderer(); + dr1->setDiagram( new QgsPieDiagram() ); + dr1->setDiagramSettings( ds1 ); + // Pie 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -652,17 +706,19 @@ class TestQgsStackedDiagram : public QgsTest ds2.rotationOffset = 270; ds2.setDirection( QgsDiagramSettings::Counterclockwise ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsPieDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsPieDiagram(), &ds2 ); + QgsSingleCategoryDiagramRenderer *dr2 = new QgsSingleCategoryDiagramRenderer(); + dr2->setDiagram( new QgsPieDiagram() ); + dr2->setDiagramSettings( ds2 ); QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Vertical; ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; - QgsSingleCategoryDiagramRenderer *dr = new QgsSingleCategoryDiagramRenderer(); - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -701,6 +757,10 @@ class TestQgsStackedDiagram : public QgsTest ds1.rotationOffset = 270; ds1.setDirection( QgsDiagramSettings::Counterclockwise ); + QgsSingleCategoryDiagramRenderer *dr1 = new QgsSingleCategoryDiagramRenderer(); + dr1->setDiagram( new QgsPieDiagram() ); + dr1->setDiagramSettings( ds1 ); + // Pie 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -723,9 +783,9 @@ class TestQgsStackedDiagram : public QgsTest ds2.rotationOffset = 270; ds2.setDirection( QgsDiagramSettings::Counterclockwise ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsPieDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsPieDiagram(), &ds2 ); + QgsSingleCategoryDiagramRenderer *dr2 = new QgsSingleCategoryDiagramRenderer(); + dr2->setDiagram( new QgsPieDiagram() ); + dr2->setDiagramSettings( ds2 ); QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Vertical; @@ -733,9 +793,11 @@ class TestQgsStackedDiagram : public QgsTest ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Points ); ds.setStackedDiagramSpacing( 8 ); - QgsSingleCategoryDiagramRenderer *dr = new QgsSingleCategoryDiagramRenderer(); - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -774,6 +836,10 @@ class TestQgsStackedDiagram : public QgsTest ds1.rotationOffset = 270; ds1.setDirection( QgsDiagramSettings::Counterclockwise ); + QgsSingleCategoryDiagramRenderer *dr1 = new QgsSingleCategoryDiagramRenderer(); + dr1->setDiagram( new QgsPieDiagram() ); + dr1->setDiagramSettings( ds1 ); + // Pie 2 QgsDiagramSettings ds2; col1 = Qt::blue; @@ -796,9 +862,9 @@ class TestQgsStackedDiagram : public QgsTest ds2.rotationOffset = 270; ds2.setDirection( QgsDiagramSettings::Counterclockwise ); - QgsStackedDiagram *stackedDiagram = new QgsStackedDiagram(); - stackedDiagram->addSubDiagram( new QgsPieDiagram(), &ds1 ); - stackedDiagram->addSubDiagram( new QgsPieDiagram(), &ds2 ); + QgsSingleCategoryDiagramRenderer *dr2 = new QgsSingleCategoryDiagramRenderer(); + dr2->setDiagram( new QgsPieDiagram() ); + dr2->setDiagramSettings( ds2 ); QgsDiagramSettings ds; ds.stackedDiagramMode = QgsDiagramSettings::Horizontal; @@ -806,9 +872,11 @@ class TestQgsStackedDiagram : public QgsTest ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Points ); ds.setStackedDiagramSpacing( 8 ); - QgsSingleCategoryDiagramRenderer *dr = new QgsSingleCategoryDiagramRenderer(); - dr->setDiagram( stackedDiagram ); + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); mPointsLayer->setDiagramRenderer( dr ); QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); @@ -903,6 +971,227 @@ class TestQgsStackedDiagram : public QgsTest QGSVERIFYRENDERMAPSETTINGSCHECK( "stackedwomenpie", "stackedwomenpie", *mMapSettings, 200, 15 ); } + void testStackedPieHistogram() + { + // Pie + QgsDiagramSettings ds1; + QColor col1 = Qt::blue; + QColor col2 = Qt::red; + QColor col3 = Qt::yellow; + QColor col4 = Qt::green; + col1.setAlphaF( 0.5 ); + col2.setAlphaF( 0.5 ); + col3.setAlphaF( 0.5 ); + col4.setAlphaF( 0.5 ); + ds1.categoryColors = QList() << col1 << col2 << col3 << col4; + ds1.categoryAttributes = QList() << QStringLiteral( "\"maennlich_ab_65\"" ) << QStringLiteral( "\"maennlich_18_64\"" ) << QStringLiteral( "\"maennlich_6_17\"" ) << QStringLiteral( "\"maennlich_unter_6\"" ); //#spellok + ds1.minimumScale = -1; + ds1.maximumScale = -1; + ds1.minimumSize = 0; + ds1.penColor = Qt::black; + ds1.penWidth = .5; + ds1.sizeType = Qgis::RenderUnit::Millimeters; + ds1.size = QSizeF( 10, 10 ); + ds1.rotationOffset = 270; + ds1.setDirection( QgsDiagramSettings::Counterclockwise ); + + QgsSingleCategoryDiagramRenderer *dr1 = new QgsSingleCategoryDiagramRenderer(); + dr1->setDiagram( new QgsPieDiagram() ); + dr1->setDiagramSettings( ds1 ); + + // Histogram + QgsDiagramSettings ds2; + col1 = Qt::blue; + col2 = Qt::red; + col3 = Qt::yellow; + col4 = Qt::green; + col1.setAlphaF( 0.5 ); + col2.setAlphaF( 0.5 ); + col3.setAlphaF( 0.5 ); + col4.setAlphaF( 0.5 ); + ds2.categoryColors = QList() << col1 << col2 << col3 << col4; + ds2.categoryAttributes = QList() << QStringLiteral( "\"weiblich_ab_65\"" ) << QStringLiteral( "\"weiblich_18_64\"" ) << QStringLiteral( "\"weiblich_6_17\"" ) << QStringLiteral( "\"weiblich_unter_6\"" ); //#spellok + ds2.minimumScale = -1; + ds2.maximumScale = -1; + ds2.minimumSize = 0; + ds2.penColor = Qt::black; + ds2.penWidth = .5; + ds2.scaleByArea = true; + ds2.sizeType = Qgis::RenderUnit::Millimeters; + ds2.rotationOffset = 0; + ds2.diagramOrientation = QgsDiagramSettings::Right; + + QgsLinearlyInterpolatedDiagramRenderer *dr2 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr2->setLowerValue( 0.0 ); + dr2->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr2->setUpperValue( 15000 ); + dr2->setUpperSize( QSizeF( 20, 20 ) ); + dr2->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + dr2->setDiagram( new QgsHistogramDiagram() ); + dr2->setDiagramSettings( ds2 ); + + QgsDiagramSettings ds; + ds.stackedDiagramMode = QgsDiagramSettings::Horizontal; + ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; + ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Points ); + ds.setStackedDiagramSpacing( 8 ); + + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); + dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); + mPointsLayer->setDiagramRenderer( dr ); + + QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); + dls.setPlacement( QgsDiagramLayerSettings::OverPoint ); + dls.setShowAllDiagrams( true ); + mPointsLayer->setDiagramLayerSettings( dls ); + + const QgsRectangle extent( 9.7, 53.5, 9.95, 53.6 ); + mMapSettings->setExtent( extent ); + mMapSettings->setFlag( Qgis::MapSettingsFlag::ForceVectorOutput ); + mMapSettings->setOutputDpi( 96 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "stackedpiehistogram", "stackedpiehistogram", *mMapSettings, 200, 15 ); + } + + void testStackedDiagramsNested() + { + // Nested stacked histograms (just because we can :)) + // 1 vertically stacked diagram: + // Above: + // + Horizontally stacked diagram + // + 2 histograms + // Below: + // + 1 pie + + // Histogram 1 + QgsDiagramSettings ds11; + QColor col1 = Qt::blue; + QColor col2 = Qt::red; + QColor col3 = Qt::yellow; + QColor col4 = Qt::green; + col1.setAlphaF( 0.5 ); + col2.setAlphaF( 0.5 ); + col3.setAlphaF( 0.5 ); + col4.setAlphaF( 0.5 ); + ds11.categoryColors = QList() << col1 << col2 << col3 << col4; + ds11.categoryAttributes = QList() << QStringLiteral( "\"maennlich_ab_65\"" ) << QStringLiteral( "\"maennlich_18_64\"" ) << QStringLiteral( "\"maennlich_6_17\"" ) << QStringLiteral( "\"maennlich_unter_6\"" ); //#spellok + ds11.minimumScale = -1; + ds11.maximumScale = -1; + ds11.minimumSize = 0; + ds11.penColor = Qt::black; + ds11.penWidth = .5; + ds11.scaleByArea = true; + ds11.sizeType = Qgis::RenderUnit::Millimeters; + ds11.barWidth = 3; + ds11.rotationOffset = 0; + ds11.diagramOrientation = QgsDiagramSettings::Left; + + QgsLinearlyInterpolatedDiagramRenderer *dr11 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr11->setDiagram( new QgsHistogramDiagram() ); + dr11->setDiagramSettings( ds11 ); + dr11->setLowerValue( 0.0 ); + dr11->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr11->setUpperValue( 15000 ); + dr11->setUpperSize( QSizeF( 20, 20 ) ); + //dr11->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"maennlich_6_17\", \"maennlich_unter_6\")" ) ); //#spellok + + // Histogram 2 + QgsDiagramSettings ds12; + col1 = Qt::blue; + col2 = Qt::red; + col3 = Qt::yellow; + col4 = Qt::green; + col1.setAlphaF( 0.5 ); + col2.setAlphaF( 0.5 ); + col3.setAlphaF( 0.5 ); + col4.setAlphaF( 0.5 ); + ds12.categoryColors = QList() << col1 << col2 << col3 << col4; + ds12.categoryAttributes = QList() << QStringLiteral( "\"weiblich_ab_65\"" ) << QStringLiteral( "\"weiblich_18_64\"" ) << QStringLiteral( "\"weiblich_6_17\"" ) << QStringLiteral( "\"weiblich_unter_6\"" ); //#spellok + ds12.minimumScale = -1; + ds12.maximumScale = -1; + ds12.minimumSize = 0; + ds12.penColor = Qt::black; + ds12.penWidth = .5; + ds12.scaleByArea = true; + ds12.barWidth = 3; + ds12.sizeType = Qgis::RenderUnit::Millimeters; + ds12.rotationOffset = 0; + ds12.diagramOrientation = QgsDiagramSettings::Right; + + QgsLinearlyInterpolatedDiagramRenderer *dr12 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr12->setDiagram( new QgsHistogramDiagram() ); + dr12->setDiagramSettings( ds12 ); + dr12->setLowerValue( 0.0 ); + dr12->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr12->setUpperValue( 15000 ); + dr12->setUpperSize( QSizeF( 20, 20 ) ); + //dr12->setClassificationField( QStringLiteral( "max(\"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + + QgsDiagramSettings ds1; + ds1.stackedDiagramMode = QgsDiagramSettings::Horizontal; + ds1.categoryAttributes = ds11.categoryAttributes + ds12.categoryAttributes; + ds1.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Pixels ); + ds1.setStackedDiagramSpacing( 0 ); + + QgsStackedDiagramRenderer *dr1 = new QgsStackedDiagramRenderer(); + dr1->setDiagram( new QgsStackedDiagram() ); + dr1->setDiagramSettings( ds1 ); + dr1->addRenderer( dr11 ); + dr1->addRenderer( dr12 ); + + // Pie + QgsDiagramSettings ds2; + col1 = Qt::blue; + col2 = Qt::red; + col3 = Qt::yellow; + col4 = Qt::green; + col1.setAlphaF( 0.5 ); + col2.setAlphaF( 0.5 ); + col3.setAlphaF( 0.5 ); + col4.setAlphaF( 0.5 ); + ds2.categoryColors = QList() << col1 << col2 << col3 << col4; + ds2.categoryAttributes = QList() << QStringLiteral( "\"gesamt_ab_65\"" ) << QStringLiteral( "\"gesamt_18_64\"" ) << QStringLiteral( "\"gesamt_6_17\"" ) << QStringLiteral( "\"gesamt_unter_6\"" ); //#spellok + ds2.minimumScale = -1; + ds2.maximumScale = -1; + ds2.minimumSize = 0; + ds2.penColor = Qt::black; + ds2.penWidth = .5; + ds2.sizeType = Qgis::RenderUnit::Millimeters; + ds2.size = QSizeF( 10, 10 ); + ds2.rotationOffset = 270; + ds2.setDirection( QgsDiagramSettings::Counterclockwise ); + + QgsSingleCategoryDiagramRenderer *dr2 = new QgsSingleCategoryDiagramRenderer(); + dr2->setDiagram( new QgsPieDiagram() ); + dr2->setDiagramSettings( ds2 ); + + QgsDiagramSettings ds; + ds.stackedDiagramMode = QgsDiagramSettings::Vertical; + ds.categoryAttributes = ds11.categoryAttributes + ds12.categoryAttributes + ds2.categoryAttributes; + ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Points ); + ds.setStackedDiagramSpacing( 4 ); + + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); + dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); + mPointsLayer->setDiagramRenderer( dr ); + + QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); + dls.setPlacement( QgsDiagramLayerSettings::OverPoint ); + dls.setShowAllDiagrams( true ); + mPointsLayer->setDiagramLayerSettings( dls ); + + const QgsRectangle extent( 9.7, 53.5, 9.95, 53.6 ); + mMapSettings->setExtent( extent ); + mMapSettings->setFlag( Qgis::MapSettingsFlag::ForceVectorOutput ); + mMapSettings->setOutputDpi( 96 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "stackeddiagramsnested", "stackeddiagramsnested", *mMapSettings, 200, 15 ); + } + }; diff --git a/tests/testdata/control_images/stackeddiagrams/expected_stackeddiagramsnested/expected_stackeddiagramsnested.png b/tests/testdata/control_images/stackeddiagrams/expected_stackeddiagramsnested/expected_stackeddiagramsnested.png new file mode 100644 index 0000000000000000000000000000000000000000..3d089da21a46ca3c6eacc3622e26c5e31a9c7c43 GIT binary patch literal 1022152 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 literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/stackeddiagrams/expected_stackedpiehistogram/expected_stackedpiehistogram.png b/tests/testdata/control_images/stackeddiagrams/expected_stackedpiehistogram/expected_stackedpiehistogram.png new file mode 100644 index 0000000000000000000000000000000000000000..1939a1bc513e65d83caf8dbc3438bd9a6fce5c56 GIT binary patch literal 1022152 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
    7OZTv=IWr?~RC) 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