QGIS API Documentation  3.27.0-Master (bef583a8ef)
qgshistogramdiagram.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgshistogramdiagram.cpp
3  ---------------------
4  begin : August 2012
5  copyright : (C) 2012 by Matthias Kuhn
6  email : matthias at opengis dot ch
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include "qgshistogramdiagram.h"
16 #include "qgsdiagramrenderer.h"
17 #include "qgsrendercontext.h"
18 #include "qgsexpression.h"
19 #include "qgssymbollayerutils.h"
20 #include "qgslinesymbol.h"
21 
22 #include <QPainter>
23 
25 {
26  mCategoryBrush.setStyle( Qt::SolidPattern );
27  mPen.setStyle( Qt::SolidLine );
28  mScaleFactor = 0;
29 }
30 
32 {
33  return new QgsHistogramDiagram( *this );
34 }
35 
37 {
38  QSizeF size;
39  if ( feature.attributes().isEmpty() )
40  {
41  return size; //zero size if no attributes
42  }
43 
44  if ( qgsDoubleNear( is.upperValue, is.lowerValue ) )
45  return size; // invalid value range => zero size
46 
47  double maxValue = 0;
48 
49  QgsExpressionContext expressionContext = c.expressionContext();
50  expressionContext.setFeature( feature );
51  if ( !feature.fields().isEmpty() )
52  expressionContext.setFields( feature.fields() );
53 
54  for ( const QString &cat : std::as_const( s.categoryAttributes ) )
55  {
56  QgsExpression *expression = getExpression( cat, expressionContext );
57  maxValue = std::max( expression->evaluate( &expressionContext ).toDouble(), maxValue );
58  }
59 
60  // Scale, if extension is smaller than the specified minimum
61  if ( maxValue < s.minimumSize )
62  {
63  maxValue = s.minimumSize;
64  }
65 
66  // eh - this method returns size in unknown units ...! We'll have to fake it and use a rough estimation of
67  // a conversion factor to painter units...
68  // TODO QGIS 4.0 -- these methods should all use painter units, dependent on the render context scaling...
69  double painterUnitConversionScale = c.convertToPainterUnits( 1, s.sizeType );
70 
71  const double spacing = c.convertToPainterUnits( s.spacing(), s.spacingUnit(), s.spacingMapUnitScale() ) / painterUnitConversionScale;
72 
73  switch ( s.diagramOrientation )
74  {
77  mScaleFactor = ( ( is.upperSize.width() - is.lowerSize.height() ) / ( is.upperValue - is.lowerValue ) );
78  size.scale( s.barWidth * s.categoryAttributes.size() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 ), maxValue * mScaleFactor, Qt::IgnoreAspectRatio );
79  break;
80 
83  mScaleFactor = ( ( is.upperSize.width() - is.lowerSize.width() ) / ( is.upperValue - is.lowerValue ) );
84  size.scale( maxValue * mScaleFactor, s.barWidth * s.categoryAttributes.size() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 ), Qt::IgnoreAspectRatio );
85  break;
86  }
87 
88  if ( s.showAxis() && s.axisLineSymbol() )
89  {
90  const double maxBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( s.axisLineSymbol(), c ) / painterUnitConversionScale;
91  size.setWidth( size.width() + 2 * maxBleed );
92  size.setHeight( size.height() + 2 * maxBleed );
93  }
94 
95  return size;
96 }
97 
99 {
100  if ( qgsDoubleNear( is.upperValue, is.lowerValue ) )
101  return s.minimumSize; // invalid value range => zero size
102 
103  // Scale, if extension is smaller than the specified minimum
104  if ( value < s.minimumSize )
105  {
106  value = s.minimumSize;
107  }
108 
109  double scaleFactor = ( ( is.upperSize.width() - is.lowerSize.width() ) / ( is.upperValue - is.lowerValue ) );
110  return value * scaleFactor;
111 }
112 
114 {
115  return DIAGRAM_NAME_HISTOGRAM;
116 }
117 
119 {
120  Q_UNUSED( c )
121  QSizeF size;
122 
123  if ( attributes.isEmpty() )
124  {
125  return QSizeF(); //zero size if no attributes
126  }
127 
128  double maxValue = attributes.at( 0 ).toDouble();
129 
130  for ( int i = 0; i < attributes.count(); ++i )
131  {
132  maxValue = std::max( attributes.at( i ).toDouble(), maxValue );
133  }
134 
135  // eh - this method returns size in unknown units ...! We'll have to fake it and use a rough estimation of
136  // a conversion factor to painter units...
137  // TODO QGIS 4.0 -- these methods should all use painter units, dependent on the render context scaling...
138  double painterUnitConversionScale = c.convertToPainterUnits( 1, s.sizeType );
139 
140  const double spacing = c.convertToPainterUnits( s.spacing(), s.spacingUnit(), s.spacingMapUnitScale() ) / painterUnitConversionScale;
141 
142  switch ( s.diagramOrientation )
143  {
146  mScaleFactor = maxValue / s.size.height();
147  size.scale( s.barWidth * s.categoryColors.size() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 ), s.size.height(), Qt::IgnoreAspectRatio );
148  break;
149 
152  mScaleFactor = maxValue / s.size.width();
153  size.scale( s.size.width(), s.barWidth * s.categoryColors.size() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 ), Qt::IgnoreAspectRatio );
154  break;
155  }
156 
157  if ( s.showAxis() && s.axisLineSymbol() )
158  {
159  const double maxBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( s.axisLineSymbol(), c ) / painterUnitConversionScale;
160  size.setWidth( size.width() + 2 * maxBleed );
161  size.setHeight( size.height() + 2 * maxBleed );
162  }
163 
164  return size;
165 }
166 
167 void QgsHistogramDiagram::renderDiagram( const QgsFeature &feature, QgsRenderContext &c, const QgsDiagramSettings &s, QPointF position )
168 {
169  QPainter *p = c.painter();
170  if ( !p )
171  {
172  return;
173  }
174 
175  QList<double> values;
176  double maxValue = 0;
177 
178  QgsExpressionContext expressionContext = c.expressionContext();
179  expressionContext.setFeature( feature );
180  if ( !feature.fields().isEmpty() )
181  expressionContext.setFields( feature.fields() );
182 
183  values.reserve( s.categoryAttributes.size() );
184  for ( const QString &cat : std::as_const( s.categoryAttributes ) )
185  {
186  QgsExpression *expression = getExpression( cat, expressionContext );
187  double currentVal = expression->evaluate( &expressionContext ).toDouble();
188  values.push_back( currentVal );
189  maxValue = std::max( currentVal, maxValue );
190  }
191 
192  double scaledMaxVal = sizePainterUnits( maxValue * mScaleFactor, s, c );
193 
194  double currentOffset = 0;
195  double scaledWidth = sizePainterUnits( s.barWidth, s, c );
196 
197  const double spacing = c.convertToPainterUnits( s.spacing(), s.spacingUnit(), s.spacingMapUnitScale() );
198 
199  double baseX = position.x();
200  double baseY = position.y();
201 
202  if ( s.showAxis() && s.axisLineSymbol() )
203  {
204  // if showing axis, the diagram position needs shifting from the default base x so that the axis
205  // line stroke sits within the desired label engine rect (otherwise we risk overlaps of the axis line stroke)
206  const double maxBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( s.axisLineSymbol(), c );
207  baseX += maxBleed;
208  baseY -= maxBleed;
209  }
210 
211 
212  mPen.setColor( s.penColor );
213  setPenWidth( mPen, s, c );
214  p->setPen( mPen );
215 
216  QList<double>::const_iterator valIt = values.constBegin();
217  QList< QColor >::const_iterator colIt = s.categoryColors.constBegin();
218  for ( ; valIt != values.constEnd(); ++valIt, ++colIt )
219  {
220  double length = sizePainterUnits( *valIt * mScaleFactor, s, c );
221 
222  mCategoryBrush.setColor( *colIt );
223  p->setBrush( mCategoryBrush );
224 
225  switch ( s.diagramOrientation )
226  {
228  p->drawRect( QRectF( baseX + currentOffset, baseY, scaledWidth, length * -1 ) );
229  break;
230 
232  p->drawRect( QRectF( baseX + currentOffset, baseY - scaledMaxVal, scaledWidth, length ) );
233  break;
234 
236  p->drawRect( QRectF( baseX, baseY - scaledWidth * values.size() - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) + currentOffset, length, scaledWidth ) );
237  break;
238 
240  p->drawRect( QRectF( baseX + scaledMaxVal, baseY - scaledWidth * values.size() - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) + currentOffset, 0 - length, scaledWidth ) );
241  break;
242  }
243 
244  currentOffset += scaledWidth + spacing;
245  }
246 
247  if ( s.showAxis() && s.axisLineSymbol() )
248  {
249  s.axisLineSymbol()->startRender( c );
250  QPolygonF axisPoints;
251  switch ( s.diagramOrientation )
252  {
254  axisPoints << QPointF( baseX, baseY - scaledMaxVal ) << QPointF( baseX, baseY ) << QPointF( baseX + scaledWidth * values.size() + spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ), baseY );
255  break;
256 
258  axisPoints << QPointF( baseX, baseY ) << QPointF( baseX, baseY - scaledMaxVal ) << QPointF( baseX + scaledWidth * values.size() + spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ), baseY - scaledMaxVal );
259  break;
260 
262  axisPoints << QPointF( baseX + scaledMaxVal, baseY - scaledWidth * values.size() - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) )
263  << QPointF( baseX, baseY - scaledWidth * values.size() - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) )
264  << QPointF( baseX, baseY );
265  break;
266 
268  axisPoints << QPointF( baseX, baseY - scaledWidth * values.size() - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) )
269  << QPointF( baseX + scaledMaxVal, baseY - scaledWidth * values.size() - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) )
270  << QPointF( baseX + scaledMaxVal, baseY );
271  break;
272  }
273 
274  s.axisLineSymbol()->renderPolyline( axisPoints, nullptr, c );
275  s.axisLineSymbol()->stopRender( c );
276  }
277 }
A vector of attributes.
Definition: qgsattributes.h:58
Additional diagram settings for interpolated size rendering.
Stores the settings for rendering a single diagram.
QgsUnitTypes::RenderUnit spacingUnit() const
Returns the units for the content spacing.
QgsUnitTypes::RenderUnit sizeType
Diagram size unit.
bool showAxis() const
Returns true if the diagram axis should be shown.
QgsLineSymbol * axisLineSymbol() const
Returns the line symbol to use for rendering axis in diagrams.
QList< QString > categoryAttributes
DiagramOrientation diagramOrientation
double spacing() const
Returns the spacing between diagram contents.
const QgsMapUnitScale & spacingMapUnitScale() const
Returns the map unit scale for the content spacing.
QList< QColor > categoryColors
double minimumSize
Scale diagrams smaller than mMinimumSize to mMinimumSize.
void setPenWidth(QPen &pen, const QgsDiagramSettings &s, const QgsRenderContext &c)
Changes the pen width to match the current settings and rendering context.
Definition: qgsdiagram.cpp:49
QSizeF sizePainterUnits(QSizeF size, const QgsDiagramSettings &s, const QgsRenderContext &c)
Calculates a size to match the current settings and rendering context.
Definition: qgsdiagram.cpp:55
QgsExpression * getExpression(const QString &expression, const QgsExpressionContext &context)
Returns a prepared expression for the specified context.
Definition: qgsdiagram.cpp:38
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setFields(const QgsFields &fields)
Convenience function for setting a fields for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
QVariant evaluate()
Evaluate the feature and return the result.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsFields fields
Definition: qgsfeature.h:66
bool isEmpty() const
Checks whether the container is empty.
Definition: qgsfields.cpp:128
A histogram style diagram.
double legendSize(double value, const QgsDiagramSettings &s, const QgsDiagramInterpolationSettings &is) const override
Returns the size of the legend item for the diagram corresponding to a specified value.
QSizeF diagramSize(const QgsAttributes &attributes, const QgsRenderContext &c, const QgsDiagramSettings &s) override
Returns the size in map units the diagram will use to render.
QString diagramName() const override
Gets a descriptive name for this diagram type.
void renderDiagram(const QgsFeature &feature, QgsRenderContext &c, const QgsDiagramSettings &s, QPointF position) override
Draws the diagram at the given position (in pixel coordinates)
QgsHistogramDiagram * clone() const override
Returns an instance that is equivalent to this one.
void renderPolyline(const QPolygonF &points, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
Renders the symbol along the line joining points, using the given render context.
Contains information about the context of a rendering operation.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
void stopRender(QgsRenderContext &context)
Ends the rendering process.
Definition: qgssymbol.cpp:844
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
Definition: qgssymbol.cpp:796
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2260
#define DIAGRAM_NAME_HISTOGRAM