QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsplot.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsplot.cpp
3  ---------------
4  begin : March 2022
5  copyright : (C) 2022 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 #include "qgsplot.h"
18 #include "qgslinesymbol.h"
19 #include "qgsfillsymbol.h"
20 #include "qgsfillsymbollayer.h"
21 #include "qgslinesymbollayer.h"
22 #include "qgstextrenderer.h"
23 #include "qgsbasicnumericformat.h"
24 #include "qgssymbollayerutils.h"
25 #include "qgsapplication.h"
28 #include <functional>
29 
30 QgsPlot::~QgsPlot() = default;
31 
32 bool QgsPlot::writeXml( QDomElement &, QDomDocument &, QgsReadWriteContext & ) const
33 {
34  return true;
35 }
36 
37 bool QgsPlot::readXml( const QDomElement &, QgsReadWriteContext & )
38 {
39  return true;
40 }
41 
42 
43 // QgsPlotAxis
44 
46 {
47  // setup default style
48 
49  mNumericFormat = std::make_unique< QgsBasicNumericFormat >();
50 
51  std::unique_ptr< QgsSimpleLineSymbolLayer > gridMinor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 50 ), 0.1 );
52  gridMinor->setPenCapStyle( Qt::FlatCap );
53  mGridMinorSymbol = std::make_unique< QgsLineSymbol>( QgsSymbolLayerList( { gridMinor.release() } ) );
54 
55  std::unique_ptr< QgsSimpleLineSymbolLayer > gridMajor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 150 ), 0.1 );
56  gridMajor->setPenCapStyle( Qt::FlatCap );
57  mGridMajorSymbol = std::make_unique< QgsLineSymbol>( QgsSymbolLayerList( { gridMajor.release() } ) );
58 }
59 
60 QgsPlotAxis::~QgsPlotAxis() = default;
61 
62 bool QgsPlotAxis::writeXml( QDomElement &element, QDomDocument &document, QgsReadWriteContext &context ) const
63 {
64  element.setAttribute( QStringLiteral( "gridIntervalMinor" ), qgsDoubleToString( mGridIntervalMinor ) );
65  element.setAttribute( QStringLiteral( "gridIntervalMajor" ), qgsDoubleToString( mGridIntervalMajor ) );
66  element.setAttribute( QStringLiteral( "labelInterval" ), qgsDoubleToString( mLabelInterval ) );
67 
68  QDomElement numericFormatElement = document.createElement( QStringLiteral( "numericFormat" ) );
69  mNumericFormat->writeXml( numericFormatElement, document, context );
70  element.appendChild( numericFormatElement );
71 
72  QDomElement gridMajorElement = document.createElement( QStringLiteral( "gridMajorSymbol" ) );
73  gridMajorElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mGridMajorSymbol.get(), document, context ) );
74  element.appendChild( gridMajorElement );
75  QDomElement gridMinorElement = document.createElement( QStringLiteral( "gridMinorSymbol" ) );
76  gridMinorElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mGridMinorSymbol.get(), document, context ) );
77  element.appendChild( gridMinorElement );
78 
79  QDomElement textFormatElement = document.createElement( QStringLiteral( "textFormat" ) );
80  textFormatElement.appendChild( mLabelTextFormat.writeXml( document, context ) );
81  element.appendChild( textFormatElement );
82 
83  return true;
84 }
85 
86 bool QgsPlotAxis::readXml( const QDomElement &element, QgsReadWriteContext &context )
87 {
88  mGridIntervalMinor = element.attribute( QStringLiteral( "gridIntervalMinor" ) ).toDouble();
89  mGridIntervalMajor = element.attribute( QStringLiteral( "gridIntervalMajor" ) ).toDouble();
90  mLabelInterval = element.attribute( QStringLiteral( "labelInterval" ) ).toDouble();
91 
92  const QDomElement numericFormatElement = element.firstChildElement( QStringLiteral( "numericFormat" ) );
93  mNumericFormat.reset( QgsApplication::numericFormatRegistry()->createFromXml( numericFormatElement, context ) );
94 
95  const QDomElement gridMajorElement = element.firstChildElement( QStringLiteral( "gridMajorSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
96  mGridMajorSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMajorElement, context ) );
97  const QDomElement gridMinorElement = element.firstChildElement( QStringLiteral( "gridMinorSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
98  mGridMinorSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMinorElement, context ) );
99 
100  const QDomElement textFormatElement = element.firstChildElement( QStringLiteral( "textFormat" ) );
101  mLabelTextFormat.readXml( textFormatElement, context );
102 
103  return true;
104 }
105 
107 {
108  return mNumericFormat.get();
109 }
110 
112 {
113  mNumericFormat.reset( format );
114 }
115 
117 {
118  return mGridMajorSymbol.get();
119 }
120 
122 {
123  mGridMajorSymbol.reset( symbol );
124 }
125 
127 {
128  return mGridMinorSymbol.get();
129 }
130 
132 {
133  mGridMinorSymbol.reset( symbol );
134 }
135 
137 {
138  return mLabelTextFormat;
139 }
140 
142 {
143  mLabelTextFormat = format;
144 }
145 
146 
147 //
148 // Qgs2DPlot
149 //
150 
152  : mMargins( 2, 2, 2, 2 )
153 {
154  // setup default style
155  std::unique_ptr< QgsSimpleFillSymbolLayer > chartFill = std::make_unique< QgsSimpleFillSymbolLayer >( QColor( 255, 255, 255 ) );
156  mChartBackgroundSymbol = std::make_unique< QgsFillSymbol>( QgsSymbolLayerList( { chartFill.release() } ) );
157 
158  std::unique_ptr< QgsSimpleLineSymbolLayer > chartBorder = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20 ), 0.1 );
159  mChartBorderSymbol = std::make_unique< QgsFillSymbol>( QgsSymbolLayerList( { chartBorder.release() } ) );
160 }
161 
162 bool Qgs2DPlot::writeXml( QDomElement &element, QDomDocument &document, QgsReadWriteContext &context ) const
163 {
164  QgsPlot::writeXml( element, document, context );
165 
166  element.setAttribute( QStringLiteral( "minX" ), qgsDoubleToString( mMinX ) );
167  element.setAttribute( QStringLiteral( "maxX" ), qgsDoubleToString( mMaxX ) );
168  element.setAttribute( QStringLiteral( "minY" ), qgsDoubleToString( mMinY ) );
169  element.setAttribute( QStringLiteral( "maxY" ), qgsDoubleToString( mMaxY ) );
170 
171  QDomElement xAxisElement = document.createElement( QStringLiteral( "xAxis" ) );
172  mXAxis.writeXml( xAxisElement, document, context );
173  element.appendChild( xAxisElement );
174  QDomElement yAxisElement = document.createElement( QStringLiteral( "yAxis" ) );
175  mYAxis.writeXml( yAxisElement, document, context );
176  element.appendChild( yAxisElement );
177 
178  QDomElement backgroundElement = document.createElement( QStringLiteral( "backgroundSymbol" ) );
179  backgroundElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBackgroundSymbol.get(), document, context ) );
180  element.appendChild( backgroundElement );
181  QDomElement borderElement = document.createElement( QStringLiteral( "borderSymbol" ) );
182  borderElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBorderSymbol.get(), document, context ) );
183  element.appendChild( borderElement );
184 
185  element.setAttribute( QStringLiteral( "margins" ), mMargins.toString() );
186 
187  return true;
188 }
189 
190 bool Qgs2DPlot::readXml( const QDomElement &element, QgsReadWriteContext &context )
191 {
192  QgsPlot::readXml( element, context );
193 
194  mMinX = element.attribute( QStringLiteral( "minX" ) ).toDouble();
195  mMaxX = element.attribute( QStringLiteral( "maxX" ) ).toDouble();
196  mMinY = element.attribute( QStringLiteral( "minY" ) ).toDouble();
197  mMaxY = element.attribute( QStringLiteral( "maxY" ) ).toDouble();
198 
199  const QDomElement xAxisElement = element.firstChildElement( QStringLiteral( "xAxis" ) );
200  mXAxis.readXml( xAxisElement, context );
201  const QDomElement yAxisElement = element.firstChildElement( QStringLiteral( "yAxis" ) );
202  mYAxis.readXml( yAxisElement, context );
203 
204  const QDomElement backgroundElement = element.firstChildElement( QStringLiteral( "backgroundSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
205  mChartBackgroundSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundElement, context ) );
206  const QDomElement borderElement = element.firstChildElement( QStringLiteral( "borderSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
207  mChartBorderSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( borderElement, context ) );
208 
209  mMargins = QgsMargins::fromString( element.attribute( QStringLiteral( "margins" ) ) );
210 
211  return true;
212 }
213 
215 {
216  QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( QStringLiteral( "plot" ) );
217  const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
218 
219  mChartBackgroundSymbol->startRender( context );
220  mChartBorderSymbol->startRender( context );
221  mXAxis.gridMinorSymbol()->startRender( context );
222  mYAxis.gridMinorSymbol()->startRender( context );
223  mXAxis.gridMajorSymbol()->startRender( context );
224  mYAxis.gridMajorSymbol()->startRender( context );
225 
226  const double firstMinorXGrid = std::ceil( mMinX / mXAxis.gridIntervalMinor() ) * mXAxis.gridIntervalMinor();
227  const double firstMajorXGrid = std::ceil( mMinX / mXAxis.gridIntervalMajor() ) * mXAxis.gridIntervalMajor();
228  const double firstMinorYGrid = std::ceil( mMinY / mYAxis.gridIntervalMinor() ) * mYAxis.gridIntervalMinor();
229  const double firstMajorYGrid = std::ceil( mMinY / mYAxis.gridIntervalMajor() ) * mYAxis.gridIntervalMajor();
230  const double firstXLabel = std::ceil( mMinX / mXAxis.labelInterval() ) * mXAxis.labelInterval();
231  const double firstYLabel = std::ceil( mMinY / mYAxis.labelInterval() ) * mYAxis.labelInterval();
232 
233  const QRectF plotArea = interiorPlotArea( context );
234 
235  const double xTolerance = mXAxis.gridIntervalMinor() / 100000;
236  const double yTolerance = mYAxis.gridIntervalMinor() / 100000;
237 
238  QgsNumericFormatContext numericContext;
239 
240  // calculate text metrics
241  double maxYAxisLabelWidth = 0;
242  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
243  for ( double currentY = firstYLabel; currentY <= mMaxY && !qgsDoubleNear( currentY, mMaxY, yTolerance ); currentY += mYAxis.labelInterval() )
244  {
245  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
246  const QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
247  maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } ) );
248  }
249 
250  const double chartAreaLeft = plotArea.left();
251  const double chartAreaRight = plotArea.right();
252  const double chartAreaTop = plotArea.top();
253  const double chartAreaBottom = plotArea.bottom();
254 
255  // chart background
256  mChartBackgroundSymbol->renderPolygon( QPolygonF(
257  {
258  QPointF( chartAreaLeft, chartAreaTop ),
259  QPointF( chartAreaRight, chartAreaTop ),
260  QPointF( chartAreaRight, chartAreaBottom ),
261  QPointF( chartAreaLeft, chartAreaBottom ),
262  QPointF( chartAreaLeft, chartAreaTop )
263  } ), nullptr, nullptr, context );
264 
265  const double xScale = ( chartAreaRight - chartAreaLeft ) / ( mMaxX - mMinX );
266  const double yScale = ( chartAreaBottom - chartAreaTop ) / ( mMaxY - mMinY );
267 
268  constexpr int MAX_OBJECTS = 1000;
269 
270  // grid lines
271 
272  // x
273  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
274  double nextMajorXGrid = firstMajorXGrid;
275  int objectNumber = 0;
276  for ( double currentX = firstMinorXGrid; objectNumber < MAX_OBJECTS && ( currentX <= mMaxX && !qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.gridIntervalMinor(), ++objectNumber )
277  {
278  bool isMinor = true;
279  if ( qgsDoubleNear( currentX, nextMajorXGrid, xTolerance ) )
280  {
281  isMinor = false;
282  nextMajorXGrid += mXAxis.gridIntervalMajor();
283  }
284 
285  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
286 
287  QgsLineSymbol *currentGridSymbol = isMinor ? mXAxis.gridMinorSymbol() : mXAxis.gridMajorSymbol();
288  currentGridSymbol->renderPolyline( QPolygonF(
289  QVector<QPointF>
290  {
291  QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaBottom ),
292  QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaTop )
293  } ), nullptr, context );
294  }
295 
296  // y
297  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
298  double nextMajorYGrid = firstMajorYGrid;
299  objectNumber = 0;
300  for ( double currentY = firstMinorYGrid; objectNumber < MAX_OBJECTS && ( currentY <= mMaxY && !qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.gridIntervalMinor(), ++objectNumber )
301  {
302  bool isMinor = true;
303  if ( qgsDoubleNear( currentY, nextMajorYGrid, yTolerance ) )
304  {
305  isMinor = false;
306  nextMajorYGrid += mYAxis.gridIntervalMajor();
307  }
308 
309  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
310 
311  QgsLineSymbol *currentGridSymbol = isMinor ? mYAxis.gridMinorSymbol() : mYAxis.gridMajorSymbol();
312  currentGridSymbol->renderPolyline( QPolygonF(
313  QVector<QPointF>
314  {
315  QPointF( chartAreaLeft, chartAreaBottom - ( currentY - mMinY ) * yScale ),
316  QPointF( chartAreaRight, chartAreaBottom - ( currentY - mMinY ) * yScale )
317  } ), nullptr, context );
318  }
319 
320  // axis labels
321 
322  // x
323  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
324  objectNumber = 0;
325  for ( double currentX = firstXLabel; objectNumber < MAX_OBJECTS && ( currentX <= mMaxX || qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.labelInterval(), ++objectNumber )
326  {
327  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
328  const QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
329  QgsTextRenderer::drawText( QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, mSize.height() - context.convertToPainterUnits( mMargins.bottom(), QgsUnitTypes::RenderMillimeters ) ),
330  0, QgsTextRenderer::AlignCenter, { text }, context, mXAxis.textFormat() );
331  }
332 
333  // y
334  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
335  objectNumber = 0;
336  for ( double currentY = firstYLabel; objectNumber < MAX_OBJECTS && ( currentY <= mMaxY || qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.labelInterval(), ++objectNumber )
337  {
338  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
339  const QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
340  const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } );
341  QgsTextRenderer::drawText( QPointF(
342  maxYAxisLabelWidth + context.convertToPainterUnits( mMargins.left(), QgsUnitTypes::RenderMillimeters ),
343  chartAreaBottom - ( currentY - mMinY ) * yScale + height / 2 ),
344  0, QgsTextRenderer::AlignRight, { text }, context, mYAxis.textFormat(), false );
345  }
346 
347  // give subclasses a chance to draw their content
348  renderContent( context, plotArea );
349 
350  // border
351  mChartBorderSymbol->renderPolygon( QPolygonF(
352  {
353  QPointF( chartAreaLeft, chartAreaTop ),
354  QPointF( chartAreaRight, chartAreaTop ),
355  QPointF( chartAreaRight, chartAreaBottom ),
356  QPointF( chartAreaLeft, chartAreaBottom ),
357  QPointF( chartAreaLeft, chartAreaTop )
358  } ), nullptr, nullptr, context );
359 
360  mChartBackgroundSymbol->stopRender( context );
361  mChartBorderSymbol->stopRender( context );
362  mXAxis.gridMinorSymbol()->stopRender( context );
363  mYAxis.gridMinorSymbol()->stopRender( context );
364  mXAxis.gridMajorSymbol()->stopRender( context );
365  mYAxis.gridMajorSymbol()->stopRender( context );
366 }
367 
369 {
370 
371 }
372 
373 Qgs2DPlot::~Qgs2DPlot() = default;
374 
375 QSizeF Qgs2DPlot::size() const
376 {
377  return mSize;
378 }
379 
380 void Qgs2DPlot::setSize( QSizeF size )
381 {
382  mSize = size;
383 }
384 
386 {
387  QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( QStringLiteral( "plot" ) );
388  const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
389 
390  const double firstMinorYGrid = std::ceil( mMinY / mYAxis.gridIntervalMinor() ) * mYAxis.gridIntervalMinor();
391  const double firstXLabel = std::ceil( mMinX / mXAxis.labelInterval() ) * mXAxis.labelInterval();
392 
393  QgsNumericFormatContext numericContext;
394 
395  const double xTolerance = mXAxis.gridIntervalMinor() / 100000;
396  const double yTolerance = mYAxis.gridIntervalMinor() / 100000;
397 
398  constexpr int MAX_LABELS = 1000;
399 
400  // calculate text metrics
401  int labelNumber = 0;
402  double maxXAxisLabelHeight = 0;
403  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
404  for ( double currentX = firstXLabel; labelNumber < MAX_LABELS && ( currentX <= mMaxX || qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.labelInterval(), labelNumber++ )
405  {
406  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
407  const QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
408  maxXAxisLabelHeight = std::max( maxXAxisLabelHeight, QgsTextRenderer::textHeight( context, mXAxis.textFormat(), { text } ) );
409  }
410 
411  double maxYAxisLabelWidth = 0;
412  labelNumber = 0;
413  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
414  for ( double currentY = firstMinorYGrid; labelNumber < MAX_LABELS && ( currentY <= mMaxY || qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.gridIntervalMinor(), labelNumber ++ )
415  {
416  plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
417  const QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
418  maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } ) );
419  }
420 
421  const double leftTextSize = maxYAxisLabelWidth + context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
422  const double rightTextSize = 0;
423  const double bottomTextSize = maxXAxisLabelHeight + context.convertToPainterUnits( 0.5, QgsUnitTypes::RenderMillimeters );
424  const double topTextSize = 0;
425 
426  const double leftMargin = context.convertToPainterUnits( mMargins.left(), QgsUnitTypes::RenderMillimeters ) + leftTextSize;
427  const double rightMargin = context.convertToPainterUnits( mMargins.right(), QgsUnitTypes::RenderMillimeters ) + rightTextSize;
428  const double topMargin = context.convertToPainterUnits( mMargins.top(), QgsUnitTypes::RenderMillimeters ) + topTextSize;
429  const double bottomMargin = context.convertToPainterUnits( mMargins.bottom(), QgsUnitTypes::RenderMillimeters ) + bottomTextSize;
430 
431  return QRectF( leftMargin, topMargin, mSize.width() - rightMargin - leftMargin, mSize.height() - bottomMargin - topMargin );
432 }
433 
435 {
436  // aim for about 40% coverage of label text to available space
437  constexpr double IDEAL_WIDTH = 0.4;
438  constexpr double TOLERANCE = 0.04;
439  constexpr int MAX_LABELS = 1000;
440 
441  const double leftMargin = context.convertToPainterUnits( mMargins.left(), QgsUnitTypes::RenderMillimeters );
442  const double rightMargin = context.convertToPainterUnits( mMargins.right(), QgsUnitTypes::RenderMillimeters );
443  const double topMargin = context.convertToPainterUnits( mMargins.top(), QgsUnitTypes::RenderMillimeters );
444  const double bottomMargin = context.convertToPainterUnits( mMargins.bottom(), QgsUnitTypes::RenderMillimeters );
445 
446  const double availableWidth = mSize.width() - leftMargin - rightMargin;
447  const double availableHeight = mSize.height() - topMargin - bottomMargin;
448 
449  QgsNumericFormatContext numericContext;
450 
451  auto refineIntervalForAxis = [&]( double axisMinimum, double axisMaximum,
452  const std::function< double( double ) > &sizeForLabel,
453  double availableSize, double idealSizePercent, double sizeTolerancePercent,
454  double & labelInterval, double & majorInterval, double & minorInterval )
455  {
456  auto roundBase10 = []( double value )->double
457  {
458  return std::pow( 10, std::floor( std::log10( value ) ) );
459  };
460 
461  // if the current interval is good enough, don't change it!
462  double totalSize = 0;
463  int initialLabelCount = 0;
464  {
465  const double firstLabelPos = std::ceil( axisMinimum / labelInterval ) * labelInterval;
466 
467  for ( double currentPos = firstLabelPos; initialLabelCount <= MAX_LABELS && currentPos <= axisMaximum; currentPos += labelInterval, ++initialLabelCount )
468  {
469  totalSize += sizeForLabel( currentPos );
470  }
471  }
472 
473  // we consider the current interval as "good enough" if it results in somewhere between 20-60% label text coverage over the size
474  if ( initialLabelCount >= MAX_LABELS || ( totalSize / availableSize < ( idealSizePercent - sizeTolerancePercent ) ) || ( totalSize / availableSize > ( idealSizePercent + sizeTolerancePercent ) ) )
475  {
476  // we start with trying to fit 30 labels in and then raise the interval till we're happy
477  int numberLabelsInitial = std::floor( availableSize / 30 );
478 
479  double labelIntervalTest = ( axisMaximum - axisMinimum ) / numberLabelsInitial;
480  double baseValue = roundBase10( labelIntervalTest );
481  double candidate = baseValue;
482  int currentMultiplier = 1;
483 
484  int numberLabels = 0;
485  while ( true )
486  {
487  const double firstLabelPosition = std::ceil( axisMinimum / candidate ) * candidate;
488  double totalSize = 0;
489  numberLabels = 0;
490  for ( double currentPos = firstLabelPosition; currentPos <= axisMaximum; currentPos += candidate )
491  {
492  totalSize += sizeForLabel( currentPos );
493  numberLabels += 1;
494 
495  if ( numberLabels > MAX_LABELS ) // avoid hangs if candidate size is very small
496  break;
497  }
498 
499  if ( numberLabels <= MAX_LABELS && totalSize <= availableSize * idealSizePercent )
500  break;
501 
502  if ( currentMultiplier == 1 )
503  currentMultiplier = 2;
504  else if ( currentMultiplier == 2 )
505  currentMultiplier = 5;
506  else if ( currentMultiplier == 5 )
507  {
508  baseValue *= 10;
509  currentMultiplier = 1;
510  }
511 
512  candidate = baseValue * currentMultiplier;
513  }
514  labelInterval = candidate;
515  if ( numberLabels < 10 )
516  {
517  minorInterval = labelInterval / 2;
518  majorInterval = minorInterval * 4;
519  }
520  else
521  {
522  minorInterval = labelInterval;
523  majorInterval = minorInterval * 5;
524  }
525  }
526  };
527 
528  {
529  double labelIntervalX = mXAxis.labelInterval();
530  double majorIntervalX = mXAxis.gridIntervalMajor();
531  double minorIntervalX = mXAxis.gridIntervalMinor();
532  refineIntervalForAxis( mMinX, mMaxX, [ = ]( double position ) -> double
533  {
534  const QString text = mXAxis.numericFormat()->formatDouble( position, numericContext );
535  return QgsTextRenderer::textWidth( context, mXAxis.textFormat(), { text } );
536 
537  }, availableWidth,
538  IDEAL_WIDTH, TOLERANCE, labelIntervalX, majorIntervalX, minorIntervalX );
539  mXAxis.setLabelInterval( labelIntervalX );
540  mXAxis.setGridIntervalMajor( majorIntervalX );
541  mXAxis.setGridIntervalMinor( minorIntervalX );
542  }
543 
544  {
545  double labelIntervalY = mYAxis.labelInterval();
546  double majorIntervalY = mYAxis.gridIntervalMajor();
547  double minorIntervalY = mYAxis.gridIntervalMinor();
548  refineIntervalForAxis( mMinY, mMaxY, [ = ]( double position ) -> double
549  {
550  const QString text = mYAxis.numericFormat()->formatDouble( position, numericContext );
551  return QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } );
552  }, availableHeight,
553  IDEAL_WIDTH, TOLERANCE, labelIntervalY, majorIntervalY, minorIntervalY );
554  mYAxis.setLabelInterval( labelIntervalY );
555  mYAxis.setGridIntervalMajor( majorIntervalY );
556  mYAxis.setGridIntervalMinor( minorIntervalY );
557  }
558 }
559 
561 {
562  return mChartBackgroundSymbol.get();
563 }
564 
566 {
567  mChartBackgroundSymbol.reset( symbol );
568 }
569 
571 {
572  return mChartBorderSymbol.get();
573 }
574 
576 {
577  mChartBorderSymbol.reset( symbol );
578 }
579 
581 {
582  return mMargins;
583 }
584 
585 void Qgs2DPlot::setMargins( const QgsMargins &margins )
586 {
587  mMargins = margins;
588 }
QgsMargins::bottom
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:90
Qgs2DPlot::render
void render(QgsRenderContext &context)
Renders the plot.
Definition: qgsplot.cpp:214
qgsexpressioncontextutils.h
QgsPlotAxis::textFormat
QgsTextFormat textFormat() const
Returns the text format used for the axis labels.
Definition: qgsplot.cpp:136
qgsnumericformatregistry.h
QgsPlotAxis::gridIntervalMajor
double gridIntervalMajor() const
Returns the interval of major grid lines for the axis.
Definition: qgsplot.h:118
QgsExpressionContextScopePopper
RAII class to pop scope from an expression context on destruction.
Definition: qgsexpressioncontextutils.h:361
QgsMargins::top
double top() const
Returns the top margin.
Definition: qgsmargins.h:78
Qgs2DPlot::interiorPlotArea
QRectF interiorPlotArea(QgsRenderContext &context) const
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition: qgsplot.cpp:385
QgsRenderContext::expressionContext
QgsExpressionContext & expressionContext()
Gets the expression context.
Definition: qgsrendercontext.h:625
Qgs2DPlot::setChartBackgroundSymbol
void setChartBackgroundSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to render the background of the chart.
Definition: qgsplot.cpp:565
QgsReadWriteContext
The class is used as a container of context for various read/write operations on other objects.
Definition: qgsreadwritecontext.h:34
QgsExpressionContextScope::addVariable
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
Definition: qgsexpressioncontext.cpp:97
QgsPlotAxis::setLabelInterval
void setLabelInterval(double interval)
Sets the interval of labels for the axis.
Definition: qgsplot.h:139
qgstextrenderer.h
Qgs2DPlot::setSize
void setSize(QSizeF size)
Sets the overall size of the plot (including titles and over components which sit outside the plot ar...
Definition: qgsplot.cpp:380
Qgs2DPlot::readXml
bool readXml(const QDomElement &element, QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
Definition: qgsplot.cpp:190
QgsTextRenderer::AlignCenter
@ AlignCenter
Center align.
Definition: qgstextrenderer.h:61
QgsTextRenderer::AlignRight
@ AlignRight
Right align.
Definition: qgstextrenderer.h:62
qgssymbollayerutils.h
Qgs2DPlot::writeXml
bool writeXml(QDomElement &element, QDomDocument &document, QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition: qgsplot.cpp:162
Qgs2DPlot::setChartBorderSymbol
void setChartBorderSymbol(QgsFillSymbol *symbol)
Sets the symbol used to render the border of the chart.
Definition: qgsplot.cpp:575
QgsRenderContext
Contains information about the context of a rendering operation.
Definition: qgsrendercontext.h:59
Qgs2DPlot::chartBorderSymbol
QgsFillSymbol * chartBorderSymbol()
Returns the symbol used to render the border of the chart.
Definition: qgsplot.cpp:570
QgsUnitTypes::RenderMillimeters
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
QgsPlotAxis::gridMajorSymbol
QgsLineSymbol * gridMajorSymbol()
Returns the line symbol used to render the major lines in the axis grid.
Definition: qgsplot.cpp:116
QgsNumericFormat
A numeric formatter allows for formatting a numeric value for display, using a variety of different f...
Definition: qgsnumericformat.h:259
Qgs2DPlot::size
QSizeF size() const
Returns the overall size of the plot (in millimeters) (including titles and other components which si...
Definition: qgsplot.cpp:375
Qgs2DPlot::renderContent
virtual void renderContent(QgsRenderContext &context, const QRectF &plotArea)
Renders the plot content.
Definition: qgsplot.cpp:368
QgsPlot::readXml
virtual bool readXml(const QDomElement &element, QgsReadWriteContext &context)
Reads the plot's properties from an XML element.
Definition: qgsplot.cpp:37
QgsPlotAxis::setGridMinorSymbol
void setGridMinorSymbol(QgsLineSymbol *symbol)
Sets the symbol used to render the minor lines in the axis grid.
Definition: qgsplot.cpp:131
qgsDoubleToString
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2204
QgsPlotAxis::readXml
bool readXml(const QDomElement &element, QgsReadWriteContext &context)
Reads the axis' properties from an XML element.
Definition: qgsplot.cpp:86
QgsTextRenderer::textHeight
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
Definition: qgstextrenderer.cpp:620
QgsTextRenderer::textWidth
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
Definition: qgstextrenderer.cpp:545
qgsapplication.h
QgsMargins::fromString
static QgsMargins fromString(const QString &string)
Returns a QgsMargins object decoded from a string, or a null QgsMargins if the string could not be in...
Definition: qgsmargins.cpp:27
QgsTextFormat
Container for all settings relating to text rendering.
Definition: qgstextformat.h:40
QgsPlotAxis::QgsPlotAxis
QgsPlotAxis()
Definition: qgsplot.cpp:45
QgsMargins::left
double left() const
Returns the left margin.
Definition: qgsmargins.h:72
Qgs2DPlot::chartBackgroundSymbol
QgsFillSymbol * chartBackgroundSymbol()
Returns the fill symbol used to render the background of the chart.
Definition: qgsplot.cpp:560
qgsplot.h
Qgs2DPlot::setMargins
void setMargins(const QgsMargins &margins)
Sets the margins of the plot area (in millimeters)
Definition: qgsplot.cpp:585
Qgs2DPlot::~Qgs2DPlot
~Qgs2DPlot() override
QgsMargins::toString
QString toString() const
Returns the margins encoded to a string.
Definition: qgsmargins.cpp:18
QgsSymbol::stopRender
void stopRender(QgsRenderContext &context)
Ends the rendering process.
Definition: qgssymbol.cpp:842
Qgs2DPlot::Qgs2DPlot
Qgs2DPlot()
Constructor for Qgs2DPlot.
Definition: qgsplot.cpp:151
QgsPlotAxis::writeXml
bool writeXml(QDomElement &element, QDomDocument &document, QgsReadWriteContext &context) const
Writes the axis' properties into an XML element.
Definition: qgsplot.cpp:62
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2265
QgsPlotAxis::setTextFormat
void setTextFormat(const QgsTextFormat &format)
Sets the text format used for the axis labels.
Definition: qgsplot.cpp:141
QgsPlot::~QgsPlot
virtual ~QgsPlot()
QgsLineSymbol
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:29
QgsTextRenderer::drawText
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, VAlignment vAlignment=AlignTop, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags())
Draws text within a rectangle using the specified settings.
Definition: qgstextrenderer.cpp:81
QgsMargins
The QgsMargins class defines the four margins of a rectangle.
Definition: qgsmargins.h:37
QgsPlot::writeXml
virtual bool writeXml(QDomElement &element, QDomDocument &document, QgsReadWriteContext &context) const
Writes the plot's properties into an XML element.
Definition: qgsplot.cpp:32
QgsPlotAxis::labelInterval
double labelInterval() const
Returns the interval of labels for the axis.
Definition: qgsplot.h:132
QgsMargins::right
double right() const
Returns the right margin.
Definition: qgsmargins.h:84
QgsTextFormat::readXml
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
Definition: qgstextformat.cpp:488
QgsNumericFormat::formatDouble
virtual QString formatDouble(double value, const QgsNumericFormatContext &context) const =0
Returns a formatted string representation of a numeric double value.
QgsSymbol::startRender
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
Definition: qgssymbol.cpp:794
qgsfillsymbollayer.h
QgsPlotAxis::~QgsPlotAxis
~QgsPlotAxis()
QgsExpressionContextScope
Single scope for storing variables and functions for use within a QgsExpressionContext....
Definition: qgsexpressioncontext.h:113
QgsPlotAxis::setGridIntervalMinor
void setGridIntervalMinor(double interval)
Sets the interval of minor grid lines for the axis.
Definition: qgsplot.h:111
QgsRenderContext::convertToPainterUnits
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
Definition: qgsrendercontext.cpp:367
qgsbasicnumericformat.h
QgsPlotAxis::setGridIntervalMajor
void setGridIntervalMajor(double interval)
Sets the interval of major grid lines for the axis.
Definition: qgsplot.h:125
qgslinesymbollayer.h
QgsPlotAxis::setNumericFormat
void setNumericFormat(QgsNumericFormat *format)
Sets the numeric format used for the axis labels.
Definition: qgsplot.cpp:111
QgsSymbolLayerList
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:27
QgsFillSymbol
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgsfillsymbol.h:29
QgsLineSymbol::renderPolyline
void renderPolyline(const QPolygonF &points, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
Renders the symbol along the line joining points, using the given render context.
Definition: qgslinesymbol.cpp:232
QgsPlotAxis::gridIntervalMinor
double gridIntervalMinor() const
Returns the interval of minor grid lines for the axis.
Definition: qgsplot.h:104
QgsApplication::numericFormatRegistry
static QgsNumericFormatRegistry * numericFormatRegistry()
Gets the registry of available numeric formats.
Definition: qgsapplication.cpp:2475
Qgs2DPlot::calculateOptimisedIntervals
void calculateOptimisedIntervals(QgsRenderContext &context)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition: qgsplot.cpp:434
Qgs2DPlot::margins
const QgsMargins & margins() const
Returns the margins of the plot area (in millimeters)
Definition: qgsplot.cpp:580
QgsExpressionContextScope::StaticVariable
Single variable definition for use within a QgsExpressionContextScope.
Definition: qgsexpressioncontext.h:120
QgsPlotAxis::setGridMajorSymbol
void setGridMajorSymbol(QgsLineSymbol *symbol)
Sets the symbol used to render the major lines in the axis grid.
Definition: qgsplot.cpp:121
qgsfillsymbol.h
QgsPlotAxis::gridMinorSymbol
QgsLineSymbol * gridMinorSymbol()
Returns the line symbol used to render the minor lines in the axis grid.
Definition: qgsplot.cpp:126
QgsTextFormat::writeXml
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
Definition: qgstextformat.cpp:671
QgsSymbolLayerUtils::saveSymbol
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
Definition: qgssymbollayerutils.cpp:1397
QgsNumericFormatContext
A context for numeric formats.
Definition: qgsnumericformat.h:34
qgslinesymbol.h
QgsPlotAxis::numericFormat
QgsNumericFormat * numericFormat() const
Returns the numeric format used for the axis labels.
Definition: qgsplot.cpp:106