QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 "qgsdiagramrendererv2.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 }
33 
35 {
36  return new QgsTextDiagram( *this );
37 }
38 
40 {
41  Q_UNUSED( c );
42 
43  QVariant attrVal;
45  {
47  attrVal = expression->evaluate( feature );
48  }
49  else
50  {
51  attrVal = feature.attributes()[is.classificationAttribute];
52  }
53 
54  if ( !attrVal.isValid() )
55  {
56  return QSizeF(); //zero size if attribute is missing
57  }
58 
59  double scaledValue = attrVal.toDouble();
60  double scaledLowerValue = is.lowerValue;
61  double scaledUpperValue = is.upperValue;
62  double scaledLowerSizeWidth = is.lowerSize.width();
63  double scaledLowerSizeHeight = is.lowerSize.height();
64  double scaledUpperSizeWidth = is.upperSize.width();
65  double scaledUpperSizeHeight = is.upperSize.height();
66 
67  // interpolate the squared value if scale by area
68  if ( s.scaleByArea )
69  {
70  scaledValue = sqrt( scaledValue );
71  scaledLowerValue = sqrt( scaledLowerValue );
72  scaledUpperValue = sqrt( scaledUpperValue );
73  scaledLowerSizeWidth = sqrt( scaledLowerSizeWidth );
74  scaledLowerSizeHeight = sqrt( scaledLowerSizeHeight );
75  scaledUpperSizeWidth = sqrt( scaledUpperSizeWidth );
76  scaledUpperSizeHeight = sqrt( scaledUpperSizeHeight );
77  }
78 
79  //interpolate size
80  double scaledRatio = ( scaledValue - scaledLowerValue ) / ( scaledUpperValue - scaledLowerValue );
81 
82  QSizeF size = QSizeF( is.upperSize.width() * scaledRatio + is.lowerSize.width() * ( 1 - scaledRatio ),
83  is.upperSize.height() * scaledRatio + is.lowerSize.height() * ( 1 - scaledRatio ) );
84 
85  // Scale, if extension is smaller than the specified minimum
86  if ( size.width() <= s.minimumSize && size.height() <= s.minimumSize )
87  {
88  size.scale( s.minimumSize, s.minimumSize, Qt::KeepAspectRatio );
89  }
90 
91  return size;
92 }
93 
94 QSizeF QgsTextDiagram::diagramSize( const QgsAttributes& attributes, const QgsRenderContext& c, const QgsDiagramSettings& s )
95 {
96  Q_UNUSED( c );
97  Q_UNUSED( attributes );
98 
99  return s.size;
100 }
101 
102 void QgsTextDiagram::renderDiagram( const QgsFeature& feature, QgsRenderContext& c, const QgsDiagramSettings& s, const QPointF& position )
103 {
104  Q_UNUSED( feature );
105 
106  QPainter* p = c.painter();
107  if ( !p )
108  {
109  return;
110  }
111 
112  //convert from mm / map units to painter units
113  QSizeF spu = sizePainterUnits( s.size, s, c );
114  double w = spu.width();
115  double h = spu.height();
116 
117  double baseX = position.x();
118  double baseY = position.y() - h;
119 
120  QList<QPointF> textPositions; //midpoints for text placement
121  int nCategories = s.categoryAttributes.size();
122  for ( int i = 0; i < nCategories; ++i )
123  {
124  if ( mOrientation == Horizontal )
125  {
126  textPositions.push_back( QPointF( baseX + ( w / nCategories ) * i + w / nCategories / 2.0, baseY + h / 2.0 ) );
127  }
128  else //vertical
129  {
130  textPositions.push_back( QPointF( baseX + w / 2.0, baseY + h / nCategories * i + w / nCategories / 2.0 ) );
131  }
132  }
133 
134  mPen.setColor( s.penColor );
135  setPenWidth( mPen, s, c );
136  p->setPen( mPen );
137  mBrush.setColor( s.backgroundColor );
138  p->setBrush( mBrush );
139 
140  //draw shapes and separator lines first
141  if ( mShape == Circle )
142  {
143  p->drawEllipse( baseX, baseY, w, h );
144 
145  //draw separator lines
146  QList<QPointF> intersect; //intersections between shape and separation lines
147  QPointF center( baseX + w / 2.0, baseY + h / 2.0 );
148  double r1 = w / 2.0; double r2 = h / 2.0;
149 
150  for ( int i = 1; i < nCategories; ++i )
151  {
152  if ( mOrientation == Horizontal )
153  {
154  lineEllipseIntersection( QPointF( baseX + w / nCategories * i, baseY ), QPointF( baseX + w / nCategories * i, baseY + h ), center, r1, r2, intersect );
155  }
156  else //vertical
157  {
158  lineEllipseIntersection( QPointF( baseX, baseY + h / nCategories * i ), QPointF( baseX + w, baseY + h / nCategories * i ), center, r1, r2, intersect );
159  }
160  if ( intersect.size() > 1 )
161  {
162  p->drawLine( intersect.at( 0 ), intersect.at( 1 ) );
163  }
164  }
165  }
166  else if ( mShape == Rectangle )
167  {
168  p->drawRect( QRectF( baseX, baseY, w, h ) );
169  for ( int i = 1; i < nCategories; ++i )
170  {
171  if ( mOrientation == Horizontal )
172  {
173  p->drawLine( QPointF( baseX + w / nCategories * i, baseY ), QPointF( baseX + w / nCategories * i, baseY + h ) );
174  }
175  else
176  {
177  p->drawLine( QPointF( baseX, baseY + h / nCategories * i ), QPointF( baseX + w, baseY + h / nCategories * i ) );
178  }
179  }
180  }
181  else //triangle
182  {
183  QPolygonF triangle;
184  triangle << QPointF( baseX, baseY + h ) << QPointF( baseX + w, baseY + h ) << QPointF( baseX + w / 2.0, baseY );
185  p->drawPolygon( triangle );
186 
187  QLineF triangleEdgeLeft( baseX + w / 2.0, baseY, baseX, baseY + h );
188  QLineF triangleEdgeRight( baseX + w, baseY + h, baseX + w / 2.0, baseY );
189  QPointF intersectionPoint1, intersectionPoint2;
190 
191  for ( int i = 1; i < nCategories; ++i )
192  {
193  if ( mOrientation == Horizontal )
194  {
195  QLineF verticalLine( baseX + w / nCategories * i, baseY + h, baseX + w / nCategories * i, baseY );
196  if ( baseX + w / nCategories * i < baseX + w / 2.0 )
197  {
198  verticalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
199  }
200  else
201  {
202  verticalLine.intersect( triangleEdgeRight, &intersectionPoint1 );
203  }
204  p->drawLine( QPointF( baseX + w / nCategories * i, baseY + h ), intersectionPoint1 );
205  }
206  else //vertical
207  {
208  QLineF horizontalLine( baseX, baseY + h / nCategories * i, baseX + w, baseY + h / nCategories * i );
209  horizontalLine.intersect( triangleEdgeLeft, &intersectionPoint1 );
210  horizontalLine.intersect( triangleEdgeRight, &intersectionPoint2 );
211  p->drawLine( intersectionPoint1, intersectionPoint2 );
212  }
213  }
214  }
215 
216  //draw text
217  QFont sFont = scaledFont( s, c );
218  QFontMetricsF fontMetrics( sFont );
219  p->setFont( sFont );
220 
221  for ( int i = 0; i < textPositions.size(); ++i )
222  {
223  QgsExpression* expression = getExpression( s.categoryAttributes.at( i ), feature.fields() );
224  QString val = expression->evaluate( feature ).toString();
225 
226  //find out dimesions
227  double textWidth = fontMetrics.width( val );
228  double textHeight = fontMetrics.height();
229 
230  mPen.setColor( s.categoryColors.at( i ) );
231  p->setPen( mPen );
232  QPointF position = textPositions.at( i );
233 
234  // Calculate vertical placement
235  double xOffset = 0;
236 
237  switch ( s.labelPlacementMethod )
238  {
240  xOffset = textHeight / 2.0;
241  break;
242 
244  xOffset = fontMetrics.xHeight();
245  break;
246  }
247  p->drawText( QPointF( position.x() - textWidth / 2.0, position.y() + xOffset ), val );
248  }
249 }
250 
251 void QgsTextDiagram::lineEllipseIntersection( const QPointF& lineStart, const QPointF& lineEnd, const QPointF& ellipseMid, double r1, double r2, QList<QPointF>& result ) const
252 {
253  result.clear();
254 
255  double rrx = r1 * r1;
256  double rry = r2 * r2;
257  double x21 = lineEnd.x() - lineStart.x();
258  double y21 = lineEnd.y() - lineStart.y();
259  double x10 = lineStart.x() - ellipseMid.x();
260  double y10 = lineStart.y() - ellipseMid.y();
261  double a = x21 * x21 / rrx + y21 * y21 / rry;
262  double b = x21 * x10 / rrx + y21 * y10 / rry;
263  double c = x10 * x10 / rrx + y10 * y10 / rry;
264  double d = b * b - a * ( c - 1 );
265  if ( d > 0 )
266  {
267  double e = sqrt( d );
268  double u1 = ( -b - e ) / a;
269  double u2 = ( -b + e ) / a;
270  //work with a tolerance of 0.00001 because of limited numerical precision
271  if ( -0.00001 <= u1 && u1 < 1.00001 )
272  {
273  result.push_back( QPointF( lineStart.x() + x21 * u1, lineStart.y() + y21 * u1 ) );
274  }
275  if ( -0.00001 <= u2 && u2 <= 1.00001 )
276  {
277  result.push_back( QPointF( lineStart.x() + x21 * u2, lineStart.y() + y21 * u2 ) );
278  }
279  }
280 }