QGIS API Documentation  3.21.0-Master (5b68dc587e)
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  const 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  const 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  const QSizeF spu = sizePainterUnits( s.size, s, c );
92  const double w = spu.width();
93  const double h = spu.height();
94 
95  const double baseX = position.x();
96  const double baseY = position.y() - h;
97 
98  QVector<QPointF> textPositions; //midpoints for text placement
99  const 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  const QPointF center( baseX + w / 2.0, baseY + h / 2.0 );
126  const double r1 = w / 2.0;
127  const 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  const QLineF triangleEdgeLeft( baseX + w / 2.0, baseY, baseX, baseY + h );
167  const 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  const 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 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
178  verticalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
179 #else
180  verticalLine.intersects( triangleEdgeLeft, &intersectionPoint1 );
181 #endif
182  }
183  else
184  {
185 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
186  verticalLine.intersect( triangleEdgeRight, &intersectionPoint1 );
187 #else
188  verticalLine.intersects( triangleEdgeRight, &intersectionPoint1 );
189 #endif
190  }
191  p->drawLine( QPointF( baseX + w / nCategories * i, baseY + h ), intersectionPoint1 );
192  }
193  else //vertical
194  {
195  const QLineF horizontalLine( baseX, baseY + h / nCategories * i, baseX + w, baseY + h / nCategories * i );
196 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
197  horizontalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
198  horizontalLine.intersect( triangleEdgeRight, &intersectionPoint2 );
199 #else
200  horizontalLine.intersects( triangleEdgeLeft, &intersectionPoint1 );
201  horizontalLine.intersects( triangleEdgeRight, &intersectionPoint2 );
202 #endif
203  p->drawLine( intersectionPoint1, intersectionPoint2 );
204  }
205  }
206  }
207 
208  //draw text
209  const QFont sFont = scaledFont( s, c );
210  const QFontMetricsF fontMetrics( sFont );
211  p->setFont( sFont );
212 
213  QgsExpressionContext expressionContext = c.expressionContext();
214  expressionContext.setFeature( feature );
215  if ( !feature.fields().isEmpty() )
216  expressionContext.setFields( feature.fields() );
217 
218  for ( int i = 0; i < textPositions.size(); ++i )
219  {
220  QgsExpression *expression = getExpression( s.categoryAttributes.at( i ), expressionContext );
221  const QString val = expression->evaluate( &expressionContext ).toString();
222 
223  //find out dimensions
224  const double textWidth = fontMetrics.horizontalAdvance( val );
225  const double textHeight = fontMetrics.height();
226 
227  mPen.setColor( s.categoryColors.at( i ) );
228  p->setPen( mPen );
229  const QPointF position = textPositions.at( i );
230 
231  // Calculate vertical placement
232  double xOffset = 0;
233 
234  switch ( s.labelPlacementMethod )
235  {
237  xOffset = textHeight / 2.0;
238  break;
239 
241  xOffset = fontMetrics.xHeight();
242  break;
243  }
244  p->drawText( QPointF( position.x() - textWidth / 2.0, position.y() + xOffset ), val );
245  }
246 }
247 
248 void QgsTextDiagram::lineEllipseIntersection( QPointF lineStart, QPointF lineEnd, QPointF ellipseMid, double r1, double r2, QList<QPointF> &result ) const
249 {
250  result.clear();
251 
252  const double rrx = r1 * r1;
253  const double rry = r2 * r2;
254  const double x21 = lineEnd.x() - lineStart.x();
255  const double y21 = lineEnd.y() - lineStart.y();
256  const double x10 = lineStart.x() - ellipseMid.x();
257  const double y10 = lineStart.y() - ellipseMid.y();
258  const double a = x21 * x21 / rrx + y21 * y21 / rry;
259  const double b = x21 * x10 / rrx + y21 * y10 / rry;
260  const double c = x10 * x10 / rrx + y10 * y10 / rry;
261  const double d = b * b - a * ( c - 1 );
262  if ( d > 0 )
263  {
264  const double e = std::sqrt( d );
265  const double u1 = ( -b - e ) / a;
266  const double u2 = ( -b + e ) / a;
267  //work with a tolerance of 0.00001 because of limited numerical precision
268  if ( -0.00001 <= u1 && u1 < 1.00001 )
269  {
270  result.push_back( QPointF( lineStart.x() + x21 * u1, lineStart.y() + y21 * u1 ) );
271  }
272  if ( -0.00001 <= u2 && u2 <= 1.00001 )
273  {
274  result.push_back( QPointF( lineStart.x() + x21 * u2, lineStart.y() + y21 * u2 ) );
275  }
276  }
277 }
A vector of attributes.
Definition: qgsattributes.h:58
Additional diagram settings for interpolated size rendering.
QString classificationField
Name of the field for classification.
Stores the settings for rendering a single diagram.
LabelPlacementMethod labelPlacementMethod
QList< QString > categoryAttributes
QList< QColor > categoryColors
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
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:81
QFont scaledFont(const QgsDiagramSettings &s, const QgsRenderContext &c)
Calculates a size to match the current settings and rendering context.
Definition: qgsdiagram.cpp:65
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
QgsFields fields
Definition: qgsfeature.h:66
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:302
bool isEmpty() const
Checks whether the container is empty.
Definition: qgsfields.cpp:128
Contains information about the context of a rendering operation.
A text based diagram.
void renderDiagram(const QgsFeature &feature, QgsRenderContext &c, const QgsDiagramSettings &s, QPointF position) override
Draws the diagram at the given position (in pixel coordinates)
QgsTextDiagram * clone() const override
Returns an instance that is equivalent to this one.
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.
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.
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
#define DIAGRAM_NAME_TEXT