QGIS API Documentation  2.99.0-Master (ae4d26a)
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 
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 std::max( size.width(), size.height() );
67 }
68 
70 {
71  return DIAGRAM_NAME_TEXT;
72 }
73 
74 QSizeF QgsTextDiagram::diagramSize( const QgsAttributes &attributes, const QgsRenderContext &c, const QgsDiagramSettings &s )
75 {
76  Q_UNUSED( c );
77  Q_UNUSED( attributes );
78 
79  return s.size;
80 }
81 
82 void QgsTextDiagram::renderDiagram( const QgsFeature &feature, QgsRenderContext &c, const QgsDiagramSettings &s, QPointF position )
83 {
84  QPainter *p = c.painter();
85  if ( !p )
86  {
87  return;
88  }
89 
90  //convert from mm / map units to painter units
91  QSizeF spu = sizePainterUnits( s.size, s, c );
92  double w = spu.width();
93  double h = spu.height();
94 
95  double baseX = position.x();
96  double baseY = position.y() - h;
97 
98  QVector<QPointF> textPositions; //midpoints for text placement
99  int nCategories = s.categoryAttributes.size();
100  for ( int i = 0; i < nCategories; ++i )
101  {
102  if ( mOrientation == Horizontal )
103  {
104  textPositions.push_back( QPointF( baseX + ( w / nCategories ) * i + w / nCategories / 2.0, baseY + h / 2.0 ) );
105  }
106  else //vertical
107  {
108  textPositions.push_back( QPointF( baseX + w / 2.0, baseY + h / nCategories * i + w / nCategories / 2.0 ) );
109  }
110  }
111 
112  mPen.setColor( s.penColor );
113  setPenWidth( mPen, s, c );
114  p->setPen( mPen );
115  mBrush.setColor( s.backgroundColor );
116  p->setBrush( mBrush );
117 
118  //draw shapes and separator lines first
119  if ( mShape == Circle )
120  {
121  p->drawEllipse( baseX, baseY, w, h );
122 
123  //draw separator lines
124  QList<QPointF> intersect; //intersections between shape and separation lines
125  QPointF center( baseX + w / 2.0, baseY + h / 2.0 );
126  double r1 = w / 2.0;
127  double r2 = h / 2.0;
128 
129  for ( int i = 1; i < nCategories; ++i )
130  {
131  if ( mOrientation == Horizontal )
132  {
133  lineEllipseIntersection( QPointF( baseX + w / nCategories * i, baseY ), QPointF( baseX + w / nCategories * i, baseY + h ), center, r1, r2, intersect );
134  }
135  else //vertical
136  {
137  lineEllipseIntersection( QPointF( baseX, baseY + h / nCategories * i ), QPointF( baseX + w, baseY + h / nCategories * i ), center, r1, r2, intersect );
138  }
139  if ( intersect.size() > 1 )
140  {
141  p->drawLine( intersect.at( 0 ), intersect.at( 1 ) );
142  }
143  }
144  }
145  else if ( mShape == Rectangle )
146  {
147  p->drawRect( QRectF( baseX, baseY, w, h ) );
148  for ( int i = 1; i < nCategories; ++i )
149  {
150  if ( mOrientation == Horizontal )
151  {
152  p->drawLine( QPointF( baseX + w / nCategories * i, baseY ), QPointF( baseX + w / nCategories * i, baseY + h ) );
153  }
154  else
155  {
156  p->drawLine( QPointF( baseX, baseY + h / nCategories * i ), QPointF( baseX + w, baseY + h / nCategories * i ) );
157  }
158  }
159  }
160  else //triangle
161  {
162  QPolygonF triangle;
163  triangle << QPointF( baseX, baseY + h ) << QPointF( baseX + w, baseY + h ) << QPointF( baseX + w / 2.0, baseY );
164  p->drawPolygon( triangle );
165 
166  QLineF triangleEdgeLeft( baseX + w / 2.0, baseY, baseX, baseY + h );
167  QLineF triangleEdgeRight( baseX + w, baseY + h, baseX + w / 2.0, baseY );
168  QPointF intersectionPoint1, intersectionPoint2;
169 
170  for ( int i = 1; i < nCategories; ++i )
171  {
172  if ( mOrientation == Horizontal )
173  {
174  QLineF verticalLine( baseX + w / nCategories * i, baseY + h, baseX + w / nCategories * i, baseY );
175  if ( baseX + w / nCategories * i < baseX + w / 2.0 )
176  {
177  verticalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
178  }
179  else
180  {
181  verticalLine.intersect( triangleEdgeRight, &intersectionPoint1 );
182  }
183  p->drawLine( QPointF( baseX + w / nCategories * i, baseY + h ), intersectionPoint1 );
184  }
185  else //vertical
186  {
187  QLineF horizontalLine( baseX, baseY + h / nCategories * i, baseX + w, baseY + h / nCategories * i );
188  horizontalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
189  horizontalLine.intersect( triangleEdgeRight, &intersectionPoint2 );
190  p->drawLine( intersectionPoint1, intersectionPoint2 );
191  }
192  }
193  }
194 
195  //draw text
196  QFont sFont = scaledFont( s, c );
197  QFontMetricsF fontMetrics( sFont );
198  p->setFont( sFont );
199 
200  QgsExpressionContext expressionContext = c.expressionContext();
201  expressionContext.setFeature( feature );
202  if ( !feature.fields().isEmpty() )
203  expressionContext.setFields( feature.fields() );
204 
205  for ( int i = 0; i < textPositions.size(); ++i )
206  {
207  QgsExpression *expression = getExpression( s.categoryAttributes.at( i ), expressionContext );
208  QString val = expression->evaluate( &expressionContext ).toString();
209 
210  //find out dimesions
211  double textWidth = fontMetrics.width( val );
212  double textHeight = fontMetrics.height();
213 
214  mPen.setColor( s.categoryColors.at( i ) );
215  p->setPen( mPen );
216  QPointF position = textPositions.at( i );
217 
218  // Calculate vertical placement
219  double xOffset = 0;
220 
221  switch ( s.labelPlacementMethod )
222  {
224  xOffset = textHeight / 2.0;
225  break;
226 
228  xOffset = fontMetrics.xHeight();
229  break;
230  }
231  p->drawText( QPointF( position.x() - textWidth / 2.0, position.y() + xOffset ), val );
232  }
233 }
234 
235 void QgsTextDiagram::lineEllipseIntersection( QPointF lineStart, QPointF lineEnd, QPointF ellipseMid, double r1, double r2, QList<QPointF> &result ) const
236 {
237  result.clear();
238 
239  double rrx = r1 * r1;
240  double rry = r2 * r2;
241  double x21 = lineEnd.x() - lineStart.x();
242  double y21 = lineEnd.y() - lineStart.y();
243  double x10 = lineStart.x() - ellipseMid.x();
244  double y10 = lineStart.y() - ellipseMid.y();
245  double a = x21 * x21 / rrx + y21 * y21 / rry;
246  double b = x21 * x10 / rrx + y21 * y10 / rry;
247  double c = x10 * x10 / rrx + y10 * y10 / rry;
248  double d = b * b - a * ( c - 1 );
249  if ( d > 0 )
250  {
251  double e = std::sqrt( d );
252  double u1 = ( -b - e ) / a;
253  double u2 = ( -b + e ) / a;
254  //work with a tolerance of 0.00001 because of limited numerical precision
255  if ( -0.00001 <= u1 && u1 < 1.00001 )
256  {
257  result.push_back( QPointF( lineStart.x() + x21 * u1, lineStart.y() + y21 * u1 ) );
258  }
259  if ( -0.00001 <= u2 && u2 <= 1.00001 )
260  {
261  result.push_back( QPointF( lineStart.x() + x21 * u2, lineStart.y() + y21 * u2 ) );
262  }
263  }
264 }
QSizeF sizePainterUnits(QSizeF size, const QgsDiagramSettings &s, const QgsRenderContext &c)
Calculates a size to match the current settings and rendering context.
Definition: qgsdiagram.cpp:58
QList< QString > categoryAttributes
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
QgsFields fields
Definition: qgsfeature.h:73
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:41
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:84
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.
QString diagramName() const override
Get a descriptive name for this diagram type.
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:68
QPainter * painter()
Returns the destination QPainter for the render operation.
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
#define DIAGRAM_NAME_TEXT
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:52
A vector of attributes.
Definition: qgsattributes.h:58
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:255
QList< QColor > categoryColors
Stores the settings for rendering a single diagram.
LabelPlacementMethod labelPlacementMethod