Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsstackedbardiagram.cpp
3 : : ---------------------
4 : : begin : November 2019
5 : : copyright : (C) 2019 by Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
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 : :
16 : : #include "qgsstackedbardiagram.h"
17 : : #include "qgsdiagramrenderer.h"
18 : : #include "qgsrendercontext.h"
19 : : #include "qgsexpression.h"
20 : : #include "qgssymbollayerutils.h"
21 : :
22 : : #include <QPainter>
23 : :
24 : 0 : QgsStackedBarDiagram::QgsStackedBarDiagram()
25 : 0 : {
26 : 0 : mCategoryBrush.setStyle( Qt::SolidPattern );
27 : 0 : mPen.setStyle( Qt::SolidLine );
28 : 0 : }
29 : :
30 : 0 : QgsStackedBarDiagram *QgsStackedBarDiagram::clone() const
31 : : {
32 : 0 : return new QgsStackedBarDiagram( *this );
33 : 0 : }
34 : :
35 : 0 : QSizeF QgsStackedBarDiagram::diagramSize( const QgsFeature &feature, const QgsRenderContext &c, const QgsDiagramSettings &s, const QgsDiagramInterpolationSettings &is )
36 : : {
37 : 0 : if ( qgsDoubleNear( is.upperValue, is.lowerValue ) )
38 : 0 : return QSizeF(); // invalid value range => zero size
39 : :
40 : 0 : QVariant attrVal;
41 : 0 : if ( is.classificationAttributeIsExpression )
42 : : {
43 : 0 : QgsExpressionContext expressionContext = c.expressionContext();
44 : 0 : if ( !feature.fields().isEmpty() )
45 : 0 : expressionContext.setFields( feature.fields() );
46 : 0 : expressionContext.setFeature( feature );
47 : :
48 : 0 : QgsExpression *expression = getExpression( is.classificationAttributeExpression, expressionContext );
49 : 0 : attrVal = expression->evaluate( &expressionContext );
50 : 0 : }
51 : : else
52 : : {
53 : 0 : attrVal = feature.attribute( is.classificationField );
54 : : }
55 : :
56 : 0 : bool ok = false;
57 : 0 : double value = fabs( attrVal.toDouble( &ok ) );
58 : 0 : if ( !ok )
59 : : {
60 : 0 : return QSizeF(); //zero size if attribute is missing
61 : : }
62 : :
63 : 0 : QSizeF size = sizeForValue( value, s, is );
64 : :
65 : : // eh - this method returns size in unknown units ...! We'll have to fake it and use a rough estimation of
66 : : // a conversion factor to painter units...
67 : : // TODO QGIS 4.0 -- these methods should all use painter units, dependent on the render context scaling...
68 : 0 : double painterUnitConversionScale = c.convertToPainterUnits( 1, s.sizeType );
69 : :
70 : 0 : const double spacing = c.convertToPainterUnits( s.spacing(), s.spacingUnit(), s.spacingMapUnitScale() ) / painterUnitConversionScale;
71 : 0 : mApplySpacingAdjust = true;
72 : :
73 : 0 : switch ( s.diagramOrientation )
74 : : {
75 : : case QgsDiagramSettings::Up:
76 : : case QgsDiagramSettings::Down:
77 : : {
78 : 0 : const double totalBarLength = size.height() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 );
79 : 0 : size = QSizeF( s.barWidth, totalBarLength );
80 : 0 : break;
81 : : }
82 : :
83 : : case QgsDiagramSettings::Right:
84 : : case QgsDiagramSettings::Left:
85 : : {
86 : 0 : const double totalBarLength = size.width() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 );
87 : 0 : size = QSizeF( totalBarLength, s.barWidth );
88 : 0 : break;
89 : : }
90 : : }
91 : :
92 : 0 : if ( s.showAxis() && s.axisLineSymbol() )
93 : : {
94 : 0 : const double maxBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( s.axisLineSymbol(), c ) / painterUnitConversionScale;
95 : 0 : size.setWidth( size.width() + 2 * maxBleed );
96 : 0 : size.setHeight( size.height() + 2 * maxBleed );
97 : 0 : }
98 : :
99 : 0 : return size;
100 : 0 : }
101 : :
102 : 0 : double QgsStackedBarDiagram::legendSize( double value, const QgsDiagramSettings &s, const QgsDiagramInterpolationSettings &is ) const
103 : : {
104 : 0 : if ( qgsDoubleNear( is.upperValue, is.lowerValue ) )
105 : 0 : return s.minimumSize; // invalid value range => zero size
106 : :
107 : : // Scale, if extension is smaller than the specified minimum
108 : 0 : if ( value < s.minimumSize )
109 : : {
110 : 0 : value = s.minimumSize;
111 : 0 : }
112 : :
113 : 0 : double scaleFactor = ( ( is.upperSize.width() - is.lowerSize.width() ) / ( is.upperValue - is.lowerValue ) );
114 : 0 : return value * scaleFactor;
115 : 0 : }
116 : :
117 : 0 : QString QgsStackedBarDiagram::diagramName() const
118 : : {
119 : 0 : return DIAGRAM_NAME_STACKED;
120 : : }
121 : :
122 : 0 : QSizeF QgsStackedBarDiagram::diagramSize( const QgsAttributes &attributes, const QgsRenderContext &c, const QgsDiagramSettings &s )
123 : : {
124 : 0 : Q_UNUSED( c )
125 : 0 : QSizeF size;
126 : :
127 : 0 : if ( attributes.isEmpty() )
128 : : {
129 : 0 : return QSizeF(); //zero size if no attributes
130 : : }
131 : :
132 : : // eh - this method returns size in unknown units ...! We'll have to fake it and use a rough estimation of
133 : : // a conversion factor to painter units...
134 : : // TODO QGIS 4.0 -- these methods should all use painter units, dependent on the render context scaling...
135 : 0 : double painterUnitConversionScale = c.convertToPainterUnits( 1, s.sizeType );
136 : :
137 : 0 : const double spacing = c.convertToPainterUnits( s.spacing(), s.spacingUnit(), s.spacingMapUnitScale() ) / painterUnitConversionScale;
138 : :
139 : 0 : switch ( s.diagramOrientation )
140 : : {
141 : : case QgsDiagramSettings::Up:
142 : : case QgsDiagramSettings::Down:
143 : 0 : size.scale( s.barWidth, s.size.height() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 ), Qt::IgnoreAspectRatio );
144 : 0 : break;
145 : :
146 : : case QgsDiagramSettings::Right:
147 : : case QgsDiagramSettings::Left:
148 : 0 : size.scale( s.size.width() + spacing * std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 ), s.barWidth, Qt::IgnoreAspectRatio );
149 : 0 : break;
150 : : }
151 : :
152 : 0 : return size;
153 : 0 : }
154 : :
155 : 0 : void QgsStackedBarDiagram::renderDiagram( const QgsFeature &feature, QgsRenderContext &c, const QgsDiagramSettings &s, QPointF position )
156 : : {
157 : 0 : QPainter *p = c.painter();
158 : 0 : if ( !p )
159 : : {
160 : 0 : return;
161 : : }
162 : :
163 : 0 : QList< QPair<double, QColor> > values;
164 : 0 : QList< QPair<double, QColor> > negativeValues;
165 : :
166 : 0 : QgsExpressionContext expressionContext = c.expressionContext();
167 : 0 : expressionContext.setFeature( feature );
168 : 0 : if ( !feature.fields().isEmpty() )
169 : 0 : expressionContext.setFields( feature.fields() );
170 : :
171 : 0 : values.reserve( s.categoryAttributes.size() );
172 : 0 : double total = 0;
173 : 0 : double negativeTotal = 0;
174 : :
175 : 0 : QList< QColor >::const_iterator colIt = s.categoryColors.constBegin();
176 : 0 : for ( const QString &cat : std::as_const( s.categoryAttributes ) )
177 : : {
178 : 0 : QgsExpression *expression = getExpression( cat, expressionContext );
179 : 0 : double currentVal = expression->evaluate( &expressionContext ).toDouble();
180 : 0 : total += fabs( currentVal );
181 : 0 : if ( currentVal >= 0 )
182 : : {
183 : 0 : values.push_back( qMakePair( currentVal, *colIt ) );
184 : 0 : }
185 : : else
186 : : {
187 : 0 : negativeTotal += currentVal;
188 : 0 : negativeValues.push_back( qMakePair( -currentVal, *colIt ) );
189 : : }
190 : 0 : ++colIt;
191 : : }
192 : :
193 : :
194 : 0 : const double spacing = c.convertToPainterUnits( s.spacing(), s.spacingUnit(), s.spacingMapUnitScale() );
195 : 0 : const double totalSpacing = std::max( 0, static_cast<int>( s.categoryAttributes.size() ) - 1 ) * spacing;
196 : :
197 : 0 : double scaledMaxVal = 0;
198 : 0 : switch ( s.diagramOrientation )
199 : : {
200 : : case QgsDiagramSettings::Up:
201 : : case QgsDiagramSettings::Down:
202 : 0 : scaledMaxVal = sizePainterUnits( s.size.height(), s, c );
203 : 0 : break;
204 : :
205 : : case QgsDiagramSettings::Right:
206 : : case QgsDiagramSettings::Left:
207 : 0 : scaledMaxVal = sizePainterUnits( s.size.width(), s, c );
208 : 0 : break;
209 : : }
210 : 0 : if ( mApplySpacingAdjust )
211 : 0 : scaledMaxVal -= totalSpacing;
212 : :
213 : 0 : double axisOffset = 0;
214 : 0 : if ( !negativeValues.isEmpty() )
215 : : {
216 : 0 : axisOffset = -negativeTotal / total * scaledMaxVal + ( negativeValues.size() - 1 ) * spacing;
217 : 0 : }
218 : 0 : double scaledWidth = sizePainterUnits( s.barWidth, s, c );
219 : :
220 : 0 : double baseX = position.x();
221 : 0 : double baseY = position.y();
222 : :
223 : 0 : if ( s.showAxis() && s.axisLineSymbol() )
224 : : {
225 : : // if showing axis, the diagram position needs shifting from the default base x so that the axis
226 : : // line stroke sits within the desired label engine rect (otherwise we risk overlaps of the axis line stroke)
227 : 0 : const double maxBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( s.axisLineSymbol(), c );
228 : 0 : baseX += maxBleed;
229 : 0 : baseY -= maxBleed;
230 : 0 : }
231 : :
232 : 0 : mPen.setColor( s.penColor );
233 : 0 : setPenWidth( mPen, s, c );
234 : 0 : p->setPen( mPen );
235 : :
236 : 0 : while ( !negativeValues.isEmpty() )
237 : : {
238 : 0 : values.push_front( negativeValues.takeLast() );
239 : : }
240 : :
241 : 0 : double currentOffset = 0;
242 : 0 : QList< QPair<double, QColor> >::const_iterator valIt = values.constBegin();
243 : 0 : for ( ; valIt != values.constEnd(); ++valIt )
244 : : {
245 : 0 : double length = valIt->first / total * scaledMaxVal;
246 : :
247 : 0 : mCategoryBrush.setColor( valIt->second );
248 : 0 : p->setBrush( mCategoryBrush );
249 : :
250 : 0 : switch ( s.diagramOrientation )
251 : : {
252 : : case QgsDiagramSettings::Up:
253 : 0 : p->drawRect( QRectF( baseX, baseY - currentOffset, scaledWidth, length * -1 ) );
254 : 0 : break;
255 : :
256 : : case QgsDiagramSettings::Down:
257 : 0 : p->drawRect( QRectF( baseX, baseY + currentOffset - scaledMaxVal - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ), scaledWidth, length ) );
258 : 0 : break;
259 : :
260 : : case QgsDiagramSettings::Right:
261 : 0 : p->drawRect( QRectF( baseX + currentOffset, baseY - scaledWidth, length, scaledWidth ) );
262 : 0 : break;
263 : :
264 : : case QgsDiagramSettings::Left:
265 : 0 : p->drawRect( QRectF( baseX + scaledMaxVal - currentOffset + spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ), baseY - scaledWidth, 0 - length, scaledWidth ) );
266 : 0 : break;
267 : : }
268 : :
269 : 0 : currentOffset += length + spacing;
270 : 0 : }
271 : :
272 : 0 : if ( s.showAxis() && s.axisLineSymbol() )
273 : : {
274 : 0 : s.axisLineSymbol()->startRender( c );
275 : 0 : QPolygonF axisPoints;
276 : 0 : switch ( s.diagramOrientation )
277 : : {
278 : : case QgsDiagramSettings::Up:
279 : 0 : axisPoints << QPointF( baseX, baseY - scaledMaxVal - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) )
280 : 0 : << QPointF( baseX, baseY - axisOffset )
281 : 0 : << QPointF( baseX + scaledWidth, baseY - axisOffset );
282 : 0 : break;
283 : :
284 : : case QgsDiagramSettings::Down:
285 : 0 : axisPoints << QPointF( baseX, baseY )
286 : 0 : << QPointF( baseX, baseY - scaledMaxVal - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) + axisOffset )
287 : 0 : << QPointF( baseX + scaledWidth, baseY - scaledMaxVal - spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) + axisOffset );
288 : 0 : break;
289 : :
290 : : case QgsDiagramSettings::Right:
291 : 0 : axisPoints << QPointF( baseX + scaledMaxVal + spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ), baseY - scaledWidth )
292 : 0 : << QPointF( baseX + axisOffset, baseY - scaledWidth )
293 : 0 : << QPointF( baseX + axisOffset, baseY );
294 : 0 : break;
295 : :
296 : : case QgsDiagramSettings::Left:
297 : 0 : axisPoints << QPointF( baseX, baseY - scaledWidth )
298 : 0 : << QPointF( baseX + scaledMaxVal + spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) - axisOffset, baseY - scaledWidth )
299 : 0 : << QPointF( baseX + scaledMaxVal + spacing * std::max( 0, static_cast<int>( values.size() ) - 1 ) - axisOffset, baseY );
300 : 0 : break;
301 : : }
302 : :
303 : 0 : s.axisLineSymbol()->renderPolyline( axisPoints, nullptr, c );
304 : 0 : s.axisLineSymbol()->stopRender( c );
305 : 0 : }
306 : 0 : }
|