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