QGIS API Documentation  2.99.0-Master (283d45a)
qgstextdiagram.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstextdiagram.cpp
3  ---------------------
4  begin : March 2011
5  copyright : (C) 2011 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole 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 "qgstextdiagram.h"
16 #include "qgsdiagramrenderer.h"
17 #include "qgsrendercontext.h"
18 #include "qgsexpression.h"
19 
20 #include <QPainter>
21 
22 QgsTextDiagram::QgsTextDiagram(): mOrientation( Vertical ), mShape( Circle )
23 {
24  mPen.setWidthF( 2.0 );
25  mPen.setColor( QColor( 0, 0, 0 ) );
26  mPen.setCapStyle( Qt::FlatCap );
27  mBrush.setStyle( Qt::SolidPattern );
28 }
29 
31 {
32  return new QgsTextDiagram( *this );
33 }
34 
36 {
37  QgsExpressionContext expressionContext = c.expressionContext();
38  expressionContext.setFeature( feature );
39  if ( !feature.fields().isEmpty() )
40  expressionContext.setFields( feature.fields() );
41 
42  QVariant attrVal;
44  {
45  QgsExpression* expression = getExpression( is.classificationAttributeExpression, expressionContext );
46  attrVal = expression->evaluate( &expressionContext );
47  }
48  else
49  {
50  attrVal = feature.attribute( is.classificationField );
51  }
52 
53  bool ok = false;
54  double val = attrVal.toDouble( &ok );
55  if ( !ok )
56  {
57  return QSizeF(); //zero size if attribute is missing
58  }
59 
60  return sizeForValue( val, s, is );
61 }
62 
63 double QgsTextDiagram::legendSize( double value, const QgsDiagramSettings &s, const QgsDiagramInterpolationSettings &is ) const
64 {
65  QSizeF size = sizeForValue( value, s, is );
66  return qMax( size.width(), size.height() );
67 }
68 
69 QSizeF QgsTextDiagram::diagramSize( const QgsAttributes& attributes, const QgsRenderContext& c, const QgsDiagramSettings& s )
70 {
71  Q_UNUSED( c );
72  Q_UNUSED( attributes );
73 
74  return s.size;
75 }
76 
77 void QgsTextDiagram::renderDiagram( const QgsFeature& feature, QgsRenderContext& c, const QgsDiagramSettings& s, QPointF position )
78 {
79  QPainter* p = c.painter();
80  if ( !p )
81  {
82  return;
83  }
84 
85  //convert from mm / map units to painter units
86  QSizeF spu = sizePainterUnits( s.size, s, c );
87  double w = spu.width();
88  double h = spu.height();
89 
90  double baseX = position.x();
91  double baseY = position.y() - h;
92 
93  QVector<QPointF> textPositions; //midpoints for text placement
94  int nCategories = s.categoryAttributes.size();
95  for ( int i = 0; i < nCategories; ++i )
96  {
97  if ( mOrientation == Horizontal )
98  {
99  textPositions.push_back( QPointF( baseX + ( w / nCategories ) * i + w / nCategories / 2.0, baseY + h / 2.0 ) );
100  }
101  else //vertical
102  {
103  textPositions.push_back( QPointF( baseX + w / 2.0, baseY + h / nCategories * i + w / nCategories / 2.0 ) );
104  }
105  }
106 
107  mPen.setColor( s.penColor );
108  setPenWidth( mPen, s, c );
109  p->setPen( mPen );
110  mBrush.setColor( s.backgroundColor );
111  p->setBrush( mBrush );
112 
113  //draw shapes and separator lines first
114  if ( mShape == Circle )
115  {
116  p->drawEllipse( baseX, baseY, w, h );
117 
118  //draw separator lines
119  QList<QPointF> intersect; //intersections between shape and separation lines
120  QPointF center( baseX + w / 2.0, baseY + h / 2.0 );
121  double r1 = w / 2.0;
122  double r2 = h / 2.0;
123 
124  for ( int i = 1; i < nCategories; ++i )
125  {
126  if ( mOrientation == Horizontal )
127  {
128  lineEllipseIntersection( QPointF( baseX + w / nCategories * i, baseY ), QPointF( baseX + w / nCategories * i, baseY + h ), center, r1, r2, intersect );
129  }
130  else //vertical
131  {
132  lineEllipseIntersection( QPointF( baseX, baseY + h / nCategories * i ), QPointF( baseX + w, baseY + h / nCategories * i ), center, r1, r2, intersect );
133  }
134  if ( intersect.size() > 1 )
135  {
136  p->drawLine( intersect.at( 0 ), intersect.at( 1 ) );
137  }
138  }
139  }
140  else if ( mShape == Rectangle )
141  {
142  p->drawRect( QRectF( baseX, baseY, w, h ) );
143  for ( int i = 1; i < nCategories; ++i )
144  {
145  if ( mOrientation == Horizontal )
146  {
147  p->drawLine( QPointF( baseX + w / nCategories * i, baseY ), QPointF( baseX + w / nCategories * i, baseY + h ) );
148  }
149  else
150  {
151  p->drawLine( QPointF( baseX, baseY + h / nCategories * i ), QPointF( baseX + w, baseY + h / nCategories * i ) );
152  }
153  }
154  }
155  else //triangle
156  {
157  QPolygonF triangle;
158  triangle << QPointF( baseX, baseY + h ) << QPointF( baseX + w, baseY + h ) << QPointF( baseX + w / 2.0, baseY );
159  p->drawPolygon( triangle );
160 
161  QLineF triangleEdgeLeft( baseX + w / 2.0, baseY, baseX, baseY + h );
162  QLineF triangleEdgeRight( baseX + w, baseY + h, baseX + w / 2.0, baseY );
163  QPointF intersectionPoint1, intersectionPoint2;
164 
165  for ( int i = 1; i < nCategories; ++i )
166  {
167  if ( mOrientation == Horizontal )
168  {
169  QLineF verticalLine( baseX + w / nCategories * i, baseY + h, baseX + w / nCategories * i, baseY );
170  if ( baseX + w / nCategories * i < baseX + w / 2.0 )
171  {
172  verticalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
173  }
174  else
175  {
176  verticalLine.intersect( triangleEdgeRight, &intersectionPoint1 );
177  }
178  p->drawLine( QPointF( baseX + w / nCategories * i, baseY + h ), intersectionPoint1 );
179  }
180  else //vertical
181  {
182  QLineF horizontalLine( baseX, baseY + h / nCategories * i, baseX + w, baseY + h / nCategories * i );
183  horizontalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
184  horizontalLine.intersect( triangleEdgeRight, &intersectionPoint2 );
185  p->drawLine( intersectionPoint1, intersectionPoint2 );
186  }
187  }
188  }
189 
190  //draw text
191  QFont sFont = scaledFont( s, c );
192  QFontMetricsF fontMetrics( sFont );
193  p->setFont( sFont );
194 
195  QgsExpressionContext expressionContext = c.expressionContext();
196  expressionContext.setFeature( feature );
197  if ( !feature.fields().isEmpty() )
198  expressionContext.setFields( feature.fields() );
199 
200  for ( int i = 0; i < textPositions.size(); ++i )
201  {
202  QgsExpression* expression = getExpression( s.categoryAttributes.at( i ), expressionContext );
203  QString val = expression->evaluate( &expressionContext ).toString();
204 
205  //find out dimesions
206  double textWidth = fontMetrics.width( val );
207  double textHeight = fontMetrics.height();
208 
209  mPen.setColor( s.categoryColors.at( i ) );
210  p->setPen( mPen );
211  QPointF position = textPositions.at( i );
212 
213  // Calculate vertical placement
214  double xOffset = 0;
215 
216  switch ( s.labelPlacementMethod )
217  {
219  xOffset = textHeight / 2.0;
220  break;
221 
223  xOffset = fontMetrics.xHeight();
224  break;
225  }
226  p->drawText( QPointF( position.x() - textWidth / 2.0, position.y() + xOffset ), val );
227  }
228 }
229 
230 void QgsTextDiagram::lineEllipseIntersection( QPointF lineStart, QPointF lineEnd, QPointF ellipseMid, double r1, double r2, QList<QPointF>& result ) const
231 {
232  result.clear();
233 
234  double rrx = r1 * r1;
235  double rry = r2 * r2;
236  double x21 = lineEnd.x() - lineStart.x();
237  double y21 = lineEnd.y() - lineStart.y();
238  double x10 = lineStart.x() - ellipseMid.x();
239  double y10 = lineStart.y() - ellipseMid.y();
240  double a = x21 * x21 / rrx + y21 * y21 / rry;
241  double b = x21 * x10 / rrx + y21 * y10 / rry;
242  double c = x10 * x10 / rrx + y10 * y10 / rry;
243  double d = b * b - a * ( c - 1 );
244  if ( d > 0 )
245  {
246  double e = sqrt( d );
247  double u1 = ( -b - e ) / a;
248  double u2 = ( -b + e ) / a;
249  //work with a tolerance of 0.00001 because of limited numerical precision
250  if ( -0.00001 <= u1 && u1 < 1.00001 )
251  {
252  result.push_back( QPointF( lineStart.x() + x21 * u1, lineStart.y() + y21 * u1 ) );
253  }
254  if ( -0.00001 <= u2 && u2 <= 1.00001 )
255  {
256  result.push_back( QPointF( lineStart.x() + x21 * u2, lineStart.y() + y21 * u2 ) );
257  }
258  }
259 }
QSizeF sizePainterUnits(QSizeF size, const QgsDiagramSettings &s, const QgsRenderContext &c)
Calculates a size to match the current settings and rendering context.
Definition: qgsdiagram.cpp:65
Class for parsing and evaluation of expressions (formerly called "search strings").
QList< QString > categoryAttributes
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
QVariant evaluate()
Evaluate the feature and return the result.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:136
QgsFields fields
Definition: qgsfeature.h:142
void renderDiagram(const QgsFeature &feature, QgsRenderContext &c, const QgsDiagramSettings &s, QPointF position) override
Draws the diagram at the given position (in pixel coordinates)
QgsExpression * getExpression(const QString &expression, const QgsExpressionContext &context)
Returns a prepared expression for the specified context.
Definition: qgsdiagram.cpp:48
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
virtual QgsTextDiagram * clone() const override
Returns an instance that is equivalent to this one.
void setFields(const QgsFields &fields)
Convenience function for setting a fields for the context.
QSizeF sizeForValue(double value, const QgsDiagramSettings &s, const QgsDiagramInterpolationSettings &is) const
Returns the scaled size of a diagram for a value, respecting the specified diagram interpolation sett...
Definition: qgsdiagram.cpp:91
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.
QgsExpressionContext & expressionContext()
Gets the expression context.
Additional diagram settings for interpolated size rendering.
Contains information about the context of a rendering operation.
QFont scaledFont(const QgsDiagramSettings &s, const QgsRenderContext &c)
Calculates a size to match the current settings and rendering context.
Definition: qgsdiagram.cpp:75
QPainter * painter()
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 classificationField
Name of the field for classification.
bool isEmpty() const
Check whether the container is empty.
Definition: qgsfields.cpp:110
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:59
A vector of attributes.
Definition: qgsfeature.h:56
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
QList< QColor > categoryColors
Stores the settings for rendering a single diagram.
LabelPlacementMethod labelPlacementMethod