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