QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 element.setAttribute( QStringLiteral( "suffix" ), mLabelSuffix );
61 element.setAttribute( QStringLiteral( "suffixPlacement" ), qgsEnumValueToKey( mSuffixPlacement ) );
62
63 QDomElement numericFormatElement = document.createElement( QStringLiteral( "numericFormat" ) );
64 mNumericFormat->writeXml( numericFormatElement, document, context );
65 element.appendChild( numericFormatElement );
66
67 QDomElement gridMajorElement = document.createElement( QStringLiteral( "gridMajorSymbol" ) );
68 gridMajorElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mGridMajorSymbol.get(), document, context ) );
69 element.appendChild( gridMajorElement );
70 QDomElement gridMinorElement = document.createElement( QStringLiteral( "gridMinorSymbol" ) );
71 gridMinorElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mGridMinorSymbol.get(), document, context ) );
72 element.appendChild( gridMinorElement );
73
74 QDomElement textFormatElement = document.createElement( QStringLiteral( "textFormat" ) );
75 textFormatElement.appendChild( mLabelTextFormat.writeXml( document, context ) );
76 element.appendChild( textFormatElement );
77
78 return true;
79}
80
81bool QgsPlotAxis::readXml( const QDomElement &element, const QgsReadWriteContext &context )
82{
83 mGridIntervalMinor = element.attribute( QStringLiteral( "gridIntervalMinor" ) ).toDouble();
84 mGridIntervalMajor = element.attribute( QStringLiteral( "gridIntervalMajor" ) ).toDouble();
85 mLabelInterval = element.attribute( QStringLiteral( "labelInterval" ) ).toDouble();
86
87 mLabelSuffix = element.attribute( QStringLiteral( "suffix" ) );
88 mSuffixPlacement = qgsEnumKeyToValue( element.attribute( QStringLiteral( "suffixPlacement" ) ), Qgis::PlotAxisSuffixPlacement::NoLabels );
89
90 const QDomElement numericFormatElement = element.firstChildElement( QStringLiteral( "numericFormat" ) );
91 mNumericFormat.reset( QgsApplication::numericFormatRegistry()->createFromXml( numericFormatElement, context ) );
92
93 const QDomElement gridMajorElement = element.firstChildElement( QStringLiteral( "gridMajorSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
94 mGridMajorSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMajorElement, context ) );
95 const QDomElement gridMinorElement = element.firstChildElement( QStringLiteral( "gridMinorSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
96 mGridMinorSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMinorElement, context ) );
97
98 const QDomElement textFormatElement = element.firstChildElement( QStringLiteral( "textFormat" ) );
99 mLabelTextFormat.readXml( textFormatElement, context );
100
101 return true;
102}
103
105{
106 return mNumericFormat.get();
107}
108
110{
111 mNumericFormat.reset( format );
112}
113
115{
116 return mLabelSuffix;
117}
118
119void QgsPlotAxis::setLabelSuffix( const QString &suffix )
120{
121 mLabelSuffix = suffix;
122}
123
125{
126 return mSuffixPlacement;
127}
128
130{
131 mSuffixPlacement = placement;
132}
133
135{
136 return mGridMajorSymbol.get();
137}
138
140{
141 mGridMajorSymbol.reset( symbol );
142}
143
145{
146 return mGridMinorSymbol.get();
147}
148
150{
151 mGridMinorSymbol.reset( symbol );
152}
153
155{
156 return mLabelTextFormat;
157}
158
160{
161 mLabelTextFormat = format;
162}
163
164
165//
166// Qgs2DPlot
167//
168
170 : mMargins( 2, 2, 2, 2 )
171{
172 // setup default style
173 mChartBackgroundSymbol.reset( QgsPlotDefaultSettings::chartBackgroundSymbol() );
174 mChartBorderSymbol.reset( QgsPlotDefaultSettings::chartBorderSymbol() );
175}
176
177bool Qgs2DPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
178{
179 QgsPlot::writeXml( element, document, context );
180
181 element.setAttribute( QStringLiteral( "minX" ), qgsDoubleToString( mMinX ) );
182 element.setAttribute( QStringLiteral( "maxX" ), qgsDoubleToString( mMaxX ) );
183 element.setAttribute( QStringLiteral( "minY" ), qgsDoubleToString( mMinY ) );
184 element.setAttribute( QStringLiteral( "maxY" ), qgsDoubleToString( mMaxY ) );
185
186 QDomElement xAxisElement = document.createElement( QStringLiteral( "xAxis" ) );
187 mXAxis.writeXml( xAxisElement, document, context );
188 element.appendChild( xAxisElement );
189 QDomElement yAxisElement = document.createElement( QStringLiteral( "yAxis" ) );
190 mYAxis.writeXml( yAxisElement, document, context );
191 element.appendChild( yAxisElement );
192
193 QDomElement backgroundElement = document.createElement( QStringLiteral( "backgroundSymbol" ) );
194 backgroundElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBackgroundSymbol.get(), document, context ) );
195 element.appendChild( backgroundElement );
196 QDomElement borderElement = document.createElement( QStringLiteral( "borderSymbol" ) );
197 borderElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBorderSymbol.get(), document, context ) );
198 element.appendChild( borderElement );
199
200 element.setAttribute( QStringLiteral( "margins" ), mMargins.toString() );
201
202 return true;
203}
204
205bool Qgs2DPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
206{
207 QgsPlot::readXml( element, context );
208
209 mMinX = element.attribute( QStringLiteral( "minX" ) ).toDouble();
210 mMaxX = element.attribute( QStringLiteral( "maxX" ) ).toDouble();
211 mMinY = element.attribute( QStringLiteral( "minY" ) ).toDouble();
212 mMaxY = element.attribute( QStringLiteral( "maxY" ) ).toDouble();
213
214 const QDomElement xAxisElement = element.firstChildElement( QStringLiteral( "xAxis" ) );
215 mXAxis.readXml( xAxisElement, context );
216 const QDomElement yAxisElement = element.firstChildElement( QStringLiteral( "yAxis" ) );
217 mYAxis.readXml( yAxisElement, context );
218
219 const QDomElement backgroundElement = element.firstChildElement( QStringLiteral( "backgroundSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
220 mChartBackgroundSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundElement, context ) );
221 const QDomElement borderElement = element.firstChildElement( QStringLiteral( "borderSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
222 mChartBorderSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( borderElement, context ) );
223
224 mMargins = QgsMargins::fromString( element.attribute( QStringLiteral( "margins" ) ) );
225
226 return true;
227}
228
230{
231 QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( QStringLiteral( "plot" ) );
232 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
233
234 mChartBackgroundSymbol->startRender( context );
235 mChartBorderSymbol->startRender( context );
236 mXAxis.gridMinorSymbol()->startRender( context );
237 mYAxis.gridMinorSymbol()->startRender( context );
238 mXAxis.gridMajorSymbol()->startRender( context );
239 mYAxis.gridMajorSymbol()->startRender( context );
240
241 const double firstMinorXGrid = std::ceil( mMinX / mXAxis.gridIntervalMinor() ) * mXAxis.gridIntervalMinor();
242 const double firstMajorXGrid = std::ceil( mMinX / mXAxis.gridIntervalMajor() ) * mXAxis.gridIntervalMajor();
243 const double firstMinorYGrid = std::ceil( mMinY / mYAxis.gridIntervalMinor() ) * mYAxis.gridIntervalMinor();
244 const double firstMajorYGrid = std::ceil( mMinY / mYAxis.gridIntervalMajor() ) * mYAxis.gridIntervalMajor();
245 const double firstXLabel = mXAxis.labelInterval() > 0 ? std::ceil( mMinX / mXAxis.labelInterval() ) * mXAxis.labelInterval() : 0;
246 const double firstYLabel = mYAxis.labelInterval() > 0 ? std::ceil( mMinY / mYAxis.labelInterval() ) * mYAxis.labelInterval() : 0;
247
248 const QString xAxisSuffix = mXAxis.labelSuffix();
249 const QString yAxisSuffix = mYAxis.labelSuffix();
250
251 const QRectF plotArea = interiorPlotArea( context );
252
253 const double xTolerance = mXAxis.gridIntervalMinor() / 100000;
254 const double yTolerance = mYAxis.gridIntervalMinor() / 100000;
255
256 QgsNumericFormatContext numericContext;
257
258 // calculate text metrics
259 double maxYAxisLabelWidth = 0;
260 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
261 if ( mYAxis.labelInterval() > 0 )
262 {
263 for ( double currentY = firstYLabel; ; currentY += mYAxis.labelInterval() )
264 {
265 const bool hasMoreLabels = currentY + mYAxis.labelInterval() <= mMaxY && !qgsDoubleNear( currentY + mYAxis.labelInterval(), mMaxY, yTolerance );
266 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
267 QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
268 switch ( mYAxis.labelSuffixPlacement() )
269 {
271 break;
272
274 text += yAxisSuffix;
275 break;
276
278 if ( currentY == firstYLabel )
279 text += yAxisSuffix;
280 break;
281
283 if ( !hasMoreLabels )
284 text += yAxisSuffix;
285 break;
286
288 if ( currentY == firstYLabel || !hasMoreLabels )
289 text += yAxisSuffix;
290 break;
291 }
292
293 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } ) );
294 if ( !hasMoreLabels )
295 break;
296 }
297 }
298
299 const double chartAreaLeft = plotArea.left();
300 const double chartAreaRight = plotArea.right();
301 const double chartAreaTop = plotArea.top();
302 const double chartAreaBottom = plotArea.bottom();
303
304 // chart background
305 mChartBackgroundSymbol->renderPolygon( QPolygonF(
306 {
307 QPointF( chartAreaLeft, chartAreaTop ),
308 QPointF( chartAreaRight, chartAreaTop ),
309 QPointF( chartAreaRight, chartAreaBottom ),
310 QPointF( chartAreaLeft, chartAreaBottom ),
311 QPointF( chartAreaLeft, chartAreaTop )
312 } ), nullptr, nullptr, context );
313
314 const double xScale = ( chartAreaRight - chartAreaLeft ) / ( mMaxX - mMinX );
315 const double yScale = ( chartAreaBottom - chartAreaTop ) / ( mMaxY - mMinY );
316
317 constexpr int MAX_OBJECTS = 1000;
318
319 // grid lines
320
321 // x
322 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
323 double nextMajorXGrid = firstMajorXGrid;
324 int objectNumber = 0;
325 for ( double currentX = firstMinorXGrid; objectNumber < MAX_OBJECTS && ( currentX <= mMaxX && !qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.gridIntervalMinor(), ++objectNumber )
326 {
327 bool isMinor = true;
328 if ( qgsDoubleNear( currentX, nextMajorXGrid, xTolerance ) )
329 {
330 isMinor = false;
331 nextMajorXGrid += mXAxis.gridIntervalMajor();
332 }
333
334 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
335
336 QgsLineSymbol *currentGridSymbol = isMinor ? mXAxis.gridMinorSymbol() : mXAxis.gridMajorSymbol();
337 currentGridSymbol->renderPolyline( QPolygonF(
338 QVector<QPointF>
339 {
340 QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaBottom ),
341 QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaTop )
342 } ), nullptr, context );
343 }
344
345 // y
346 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
347 double nextMajorYGrid = firstMajorYGrid;
348 objectNumber = 0;
349 for ( double currentY = firstMinorYGrid; objectNumber < MAX_OBJECTS && ( currentY <= mMaxY && !qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.gridIntervalMinor(), ++objectNumber )
350 {
351 bool isMinor = true;
352 if ( qgsDoubleNear( currentY, nextMajorYGrid, yTolerance ) )
353 {
354 isMinor = false;
355 nextMajorYGrid += mYAxis.gridIntervalMajor();
356 }
357
358 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
359
360 QgsLineSymbol *currentGridSymbol = isMinor ? mYAxis.gridMinorSymbol() : mYAxis.gridMajorSymbol();
361 currentGridSymbol->renderPolyline( QPolygonF(
362 QVector<QPointF>
363 {
364 QPointF( chartAreaLeft, chartAreaBottom - ( currentY - mMinY ) * yScale ),
365 QPointF( chartAreaRight, chartAreaBottom - ( currentY - mMinY ) * yScale )
366 } ), nullptr, context );
367 }
368
369 // axis labels
370
371 // x
372 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
373 objectNumber = 0;
374 if ( mXAxis.labelInterval() > 0 )
375 {
376 for ( double currentX = firstXLabel; ; currentX += mXAxis.labelInterval(), ++objectNumber )
377 {
378 const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentX + mXAxis.labelInterval() <= mMaxX || qgsDoubleNear( currentX + mXAxis.labelInterval(), mMaxX, xTolerance ) );
379 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
380 QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
381 switch ( mXAxis.labelSuffixPlacement() )
382 {
384 break;
385
387 text += xAxisSuffix;
388 break;
389
391 if ( objectNumber == 0 )
392 text += xAxisSuffix;
393 break;
394
396 if ( !hasMoreLabels )
397 text += xAxisSuffix;
398 break;
399
401 if ( objectNumber == 0 || !hasMoreLabels )
402 text += xAxisSuffix;
403 break;
404 }
405
406 QgsTextRenderer::drawText( QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, mSize.height() - context.convertToPainterUnits( mMargins.bottom(), Qgis::RenderUnit::Millimeters ) ),
407 0, Qgis::TextHorizontalAlignment::Center, { text }, context, mXAxis.textFormat() );
408 if ( !hasMoreLabels )
409 break;
410 }
411 }
412
413 // y
414 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
415 objectNumber = 0;
416 if ( mYAxis.labelInterval() > 0 )
417 {
418 for ( double currentY = firstYLabel; ; currentY += mYAxis.labelInterval(), ++objectNumber )
419 {
420 const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentY + mYAxis.labelInterval() <= mMaxY || qgsDoubleNear( currentY + mYAxis.labelInterval(), mMaxY, yTolerance ) );
421 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
422 QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
423 switch ( mYAxis.labelSuffixPlacement() )
424 {
426 break;
427
429 text += yAxisSuffix;
430 break;
431
433 if ( objectNumber == 0 )
434 text += yAxisSuffix;
435 break;
436
438 if ( !hasMoreLabels )
439 text += yAxisSuffix;
440 break;
441
443 if ( objectNumber == 0 || !hasMoreLabels )
444 text += yAxisSuffix;
445 break;
446 }
447
448 const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } );
450 maxYAxisLabelWidth + context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters ),
451 chartAreaBottom - ( currentY - mMinY ) * yScale + height / 2 ),
452 0, Qgis::TextHorizontalAlignment::Right, { text }, context, mYAxis.textFormat(), false );
453 if ( !hasMoreLabels )
454 break;
455 }
456 }
457
458 // give subclasses a chance to draw their content
459 renderContent( context, plotArea );
460
461 // border
462 mChartBorderSymbol->renderPolygon( QPolygonF(
463 {
464 QPointF( chartAreaLeft, chartAreaTop ),
465 QPointF( chartAreaRight, chartAreaTop ),
466 QPointF( chartAreaRight, chartAreaBottom ),
467 QPointF( chartAreaLeft, chartAreaBottom ),
468 QPointF( chartAreaLeft, chartAreaTop )
469 } ), nullptr, nullptr, context );
470
471 mChartBackgroundSymbol->stopRender( context );
472 mChartBorderSymbol->stopRender( context );
473 mXAxis.gridMinorSymbol()->stopRender( context );
474 mYAxis.gridMinorSymbol()->stopRender( context );
475 mXAxis.gridMajorSymbol()->stopRender( context );
476 mYAxis.gridMajorSymbol()->stopRender( context );
477}
478
480{
481
482}
483
484Qgs2DPlot::~Qgs2DPlot() = default;
485
486QSizeF Qgs2DPlot::size() const
487{
488 return mSize;
489}
490
491void Qgs2DPlot::setSize( QSizeF size )
492{
493 mSize = size;
494}
495
497{
498 QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( QStringLiteral( "plot" ) );
499 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
500
501 const double firstMinorYGrid = std::ceil( mMinY / mYAxis.gridIntervalMinor() ) * mYAxis.gridIntervalMinor();
502 const double firstXLabel = mXAxis.labelInterval() > 0 ? std::ceil( mMinX / mXAxis.labelInterval() ) * mXAxis.labelInterval() : 0;
503
504 const QString xAxisSuffix = mXAxis.labelSuffix();
505 const QString yAxisSuffix = mYAxis.labelSuffix();
506 const double yAxisSuffixWidth = yAxisSuffix.isEmpty() ? 0 : QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { yAxisSuffix } );
507
508 QgsNumericFormatContext numericContext;
509
510 const double xTolerance = mXAxis.gridIntervalMinor() / 100000;
511 const double yTolerance = mYAxis.gridIntervalMinor() / 100000;
512
513 constexpr int MAX_LABELS = 1000;
514
515 // calculate text metrics
516 int labelNumber = 0;
517 double maxXAxisLabelHeight = 0;
518 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
519 if ( mXAxis.labelInterval() > 0 )
520 {
521 for ( double currentX = firstXLabel; ; currentX += mXAxis.labelInterval(), labelNumber++ )
522 {
523 const bool hasMoreLabels = labelNumber + 1 < MAX_LABELS && ( currentX + mXAxis.labelInterval() <= mMaxX || qgsDoubleNear( currentX + mXAxis.labelInterval(), mMaxX, xTolerance ) );
524
525 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
526 QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
527 switch ( mXAxis.labelSuffixPlacement() )
528 {
530 break;
531
533 text += xAxisSuffix;
534 break;
535
537 if ( labelNumber == 0 )
538 text += xAxisSuffix;
539 break;
540
542 if ( !hasMoreLabels )
543 text += xAxisSuffix;
544 break;
545
547 if ( labelNumber == 0 || !hasMoreLabels )
548 text += xAxisSuffix;
549 break;
550 }
551 maxXAxisLabelHeight = std::max( maxXAxisLabelHeight, QgsTextRenderer::textHeight( context, mXAxis.textFormat(), { text } ) );
552 if ( !hasMoreLabels )
553 break;
554 }
555 }
556
557 double maxYAxisLabelWidth = 0;
558 labelNumber = 0;
559 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
560 for ( double currentY = firstMinorYGrid; ; currentY += mYAxis.gridIntervalMinor(), labelNumber ++ )
561 {
562 const bool hasMoreLabels = labelNumber + 1 < MAX_LABELS && ( currentY + mYAxis.gridIntervalMinor() <= mMaxY || qgsDoubleNear( currentY + mYAxis.gridIntervalMinor(), mMaxY, yTolerance ) );
563 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
564 const QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
565 double thisLabelWidth = QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } );
566 if ( yAxisSuffixWidth > 0 )
567 {
568 switch ( mYAxis.labelSuffixPlacement() )
569 {
571 break;
572
574 thisLabelWidth += yAxisSuffixWidth;
575 break;
576
578 if ( labelNumber == 0 )
579 thisLabelWidth += yAxisSuffixWidth;
580 break;
581
583 if ( !hasMoreLabels )
584 thisLabelWidth += yAxisSuffixWidth;
585 break;
586
588 if ( labelNumber == 0 || !hasMoreLabels )
589 thisLabelWidth += yAxisSuffixWidth;
590 break;
591 }
592 }
593 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, thisLabelWidth );
594 if ( !hasMoreLabels )
595 break;
596 }
597
598 const double leftTextSize = maxYAxisLabelWidth + context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
599 const double rightTextSize = 0;
600 const double bottomTextSize = maxXAxisLabelHeight + context.convertToPainterUnits( 0.5, Qgis::RenderUnit::Millimeters );
601 const double topTextSize = 0;
602
603 const double leftMargin = context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters ) + leftTextSize;
604 const double rightMargin = context.convertToPainterUnits( mMargins.right(), Qgis::RenderUnit::Millimeters ) + rightTextSize;
605 const double topMargin = context.convertToPainterUnits( mMargins.top(), Qgis::RenderUnit::Millimeters ) + topTextSize;
606 const double bottomMargin = context.convertToPainterUnits( mMargins.bottom(), Qgis::RenderUnit::Millimeters ) + bottomTextSize;
607
608 return QRectF( leftMargin, topMargin, mSize.width() - rightMargin - leftMargin, mSize.height() - bottomMargin - topMargin );
609}
610
612{
613 if ( !mSize.isValid() )
614 return;
615
616 // aim for about 40% coverage of label text to available space
617 constexpr double IDEAL_WIDTH = 0.4;
618 constexpr double TOLERANCE = 0.04;
619 constexpr int MAX_LABELS = 1000;
620
621 const double leftMargin = context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters );
622 const double rightMargin = context.convertToPainterUnits( mMargins.right(), Qgis::RenderUnit::Millimeters );
623 const double topMargin = context.convertToPainterUnits( mMargins.top(), Qgis::RenderUnit::Millimeters );
624 const double bottomMargin = context.convertToPainterUnits( mMargins.bottom(), Qgis::RenderUnit::Millimeters );
625
626 const double availableWidth = mSize.width() - leftMargin - rightMargin;
627 const double availableHeight = mSize.height() - topMargin - bottomMargin;
628
629 QgsNumericFormatContext numericContext;
630
631 auto refineIntervalForAxis = [&]( double axisMinimum, double axisMaximum,
632 const std::function< double( double ) > &sizeForLabel,
633 double availableSize, double idealSizePercent, double sizeTolerancePercent,
634 double & labelInterval, double & majorInterval, double & minorInterval )
635 {
636 auto roundBase10 = []( double value )->double
637 {
638 return std::pow( 10, std::floor( std::log10( value ) ) );
639 };
640
641 // if the current interval is good enough, don't change it!
642 double totalSize = 0;
643 int initialLabelCount = 0;
644 {
645 const double firstLabelPos = std::ceil( axisMinimum / labelInterval ) * labelInterval;
646
647 for ( double currentPos = firstLabelPos; initialLabelCount <= MAX_LABELS && currentPos <= axisMaximum; currentPos += labelInterval, ++initialLabelCount )
648 {
649 totalSize += sizeForLabel( currentPos );
650 }
651 }
652
653 // we consider the current interval as "good enough" if it results in somewhere between 20-60% label text coverage over the size
654 if ( initialLabelCount >= MAX_LABELS || ( totalSize / availableSize < ( idealSizePercent - sizeTolerancePercent ) ) || ( totalSize / availableSize > ( idealSizePercent + sizeTolerancePercent ) ) )
655 {
656 // we start with trying to fit 30 labels in and then raise the interval till we're happy
657 int numberLabelsInitial = std::floor( availableSize / 30 );
658
659 double labelIntervalTest = ( axisMaximum - axisMinimum ) / numberLabelsInitial;
660 double baseValue = roundBase10( labelIntervalTest );
661 double candidate = baseValue;
662 int currentMultiplier = 1;
663
664 int numberLabels = 0;
665 while ( true )
666 {
667 const double firstLabelPosition = std::ceil( axisMinimum / candidate ) * candidate;
668 double totalSize = 0;
669 numberLabels = 0;
670 for ( double currentPos = firstLabelPosition; currentPos <= axisMaximum; currentPos += candidate )
671 {
672 totalSize += sizeForLabel( currentPos );
673 numberLabels += 1;
674
675 if ( numberLabels > MAX_LABELS ) // avoid hangs if candidate size is very small
676 break;
677 }
678
679 if ( numberLabels <= MAX_LABELS && totalSize <= availableSize * idealSizePercent )
680 break;
681
682 if ( currentMultiplier == 1 )
683 currentMultiplier = 2;
684 else if ( currentMultiplier == 2 )
685 currentMultiplier = 5;
686 else if ( currentMultiplier == 5 )
687 {
688 baseValue *= 10;
689 currentMultiplier = 1;
690 }
691
692 candidate = baseValue * currentMultiplier;
693 }
694 labelInterval = candidate;
695 if ( numberLabels < 10 )
696 {
697 minorInterval = labelInterval / 2;
698 majorInterval = minorInterval * 4;
699 }
700 else
701 {
702 minorInterval = labelInterval;
703 majorInterval = minorInterval * 5;
704 }
705 }
706 };
707
708 {
709 double labelIntervalX = mXAxis.labelInterval();
710 double majorIntervalX = mXAxis.gridIntervalMajor();
711 double minorIntervalX = mXAxis.gridIntervalMinor();
712 const QString suffixX = mXAxis.labelSuffix();
713 const double suffixWidth = !suffixX.isEmpty() ? QgsTextRenderer::textWidth( context, mXAxis.textFormat(), { suffixX } ) : 0;
714 refineIntervalForAxis( mMinX, mMaxX, [this, &context, suffixWidth, &numericContext]( double position ) -> double
715 {
716 const QString text = mXAxis.numericFormat()->formatDouble( position, numericContext );
717 // this isn't accurate, as we're always considering the suffix to be present... but it's too tricky to actually consider
718 // the suffix placement!
719 return QgsTextRenderer::textWidth( context, mXAxis.textFormat(), { text } ) + suffixWidth;
720 }, availableWidth,
721 IDEAL_WIDTH, TOLERANCE, labelIntervalX, majorIntervalX, minorIntervalX );
722 mXAxis.setLabelInterval( labelIntervalX );
723 mXAxis.setGridIntervalMajor( majorIntervalX );
724 mXAxis.setGridIntervalMinor( minorIntervalX );
725 }
726
727 {
728 double labelIntervalY = mYAxis.labelInterval();
729 double majorIntervalY = mYAxis.gridIntervalMajor();
730 double minorIntervalY = mYAxis.gridIntervalMinor();
731 const QString suffixY = mYAxis.labelSuffix();
732 refineIntervalForAxis( mMinY, mMaxY, [this, &context, suffixY, &numericContext]( double position ) -> double
733 {
734 const QString text = mYAxis.numericFormat()->formatDouble( position, numericContext );
735 // this isn't accurate, as we're always considering the suffix to be present... but it's too tricky to actually consider
736 // the suffix placement!
737 return QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text + suffixY } );
738 }, availableHeight,
739 IDEAL_WIDTH, TOLERANCE, labelIntervalY, majorIntervalY, minorIntervalY );
740 mYAxis.setLabelInterval( labelIntervalY );
741 mYAxis.setGridIntervalMajor( majorIntervalY );
742 mYAxis.setGridIntervalMinor( minorIntervalY );
743 }
744}
745
747{
748 return mChartBackgroundSymbol.get();
749}
750
752{
753 mChartBackgroundSymbol.reset( symbol );
754}
755
757{
758 return mChartBorderSymbol.get();
759}
760
762{
763 mChartBorderSymbol.reset( symbol );
764}
765
767{
768 return mMargins;
769}
770
771void Qgs2DPlot::setMargins( const QgsMargins &margins )
772{
773 mMargins = margins;
774}
775
776//
777// QgsPlotDefaultSettings
778//
779
781{
782 return new QgsBasicNumericFormat();
783}
784
786{
787 std::unique_ptr< QgsSimpleLineSymbolLayer > gridMajor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 150 ), 0.1 );
788 gridMajor->setPenCapStyle( Qt::FlatCap );
789 return new QgsLineSymbol( QgsSymbolLayerList( { gridMajor.release() } ) );
790}
791
793{
794 std::unique_ptr< QgsSimpleLineSymbolLayer > gridMinor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 50 ), 0.1 );
795 gridMinor->setPenCapStyle( Qt::FlatCap );
796 return new QgsLineSymbol( QgsSymbolLayerList( { gridMinor.release() } ) );
797}
798
800{
801 std::unique_ptr< QgsSimpleFillSymbolLayer > chartFill = std::make_unique< QgsSimpleFillSymbolLayer >( QColor( 255, 255, 255 ) );
802 return new QgsFillSymbol( QgsSymbolLayerList( { chartFill.release() } ) );
803}
804
806{
807 std::unique_ptr< QgsSimpleLineSymbolLayer > chartBorder = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20 ), 0.1 );
808 return new QgsFillSymbol( QgsSymbolLayerList( { chartBorder.release() } ) );
809}
PlotAxisSuffixPlacement
Placement options for suffixes in the labels for axis of plots.
Definition: qgis.h:2702
@ FirstAndLastLabels
Place suffix after the first and last label values only.
@ EveryLabel
Place suffix after every value label.
@ FirstLabel
Place suffix after the first label value only.
@ LastLabel
Place suffix after the last label value only.
@ NoLabels
Do not place suffixes.
@ Millimeters
Millimeters.
void calculateOptimisedIntervals(QgsRenderContext &context)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition: qgsplot.cpp:611
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition: qgsplot.cpp:177
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:491
const QgsMargins & margins() const
Returns the margins of the plot area (in millimeters)
Definition: qgsplot.cpp:766
~Qgs2DPlot() override
void render(QgsRenderContext &context)
Renders the plot.
Definition: qgsplot.cpp:229
void setChartBorderSymbol(QgsFillSymbol *symbol)
Sets the symbol used to render the border of the chart.
Definition: qgsplot.cpp:761
Qgs2DPlot()
Constructor for Qgs2DPlot.
Definition: qgsplot.cpp:169
bool readXml(const QDomElement &element, const QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
Definition: qgsplot.cpp:205
QgsFillSymbol * chartBorderSymbol()
Returns the symbol used to render the border of the chart.
Definition: qgsplot.cpp:756
QSizeF size() const
Returns the overall size of the plot (in millimeters) (including titles and other components which si...
Definition: qgsplot.cpp:486
void setMargins(const QgsMargins &margins)
Sets the margins of the plot area (in millimeters)
Definition: qgsplot.cpp:771
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:496
void setChartBackgroundSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to render the background of the chart.
Definition: qgsplot.cpp:751
virtual void renderContent(QgsRenderContext &context, const QRectF &plotArea)
Renders the plot content.
Definition: qgsplot.cpp:479
QgsFillSymbol * chartBackgroundSymbol()
Returns the fill symbol used to render the background of the chart.
Definition: qgsplot.cpp:746
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:37
double top() const
Returns the top margin.
Definition: qgsmargins.h:77
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:83
double bottom() const
Returns the bottom margin.
Definition: qgsmargins.h:89
QString toString() const
Returns the margins encoded to a string.
Definition: qgsmargins.cpp:18
double left() const
Returns the left margin.
Definition: qgsmargins.h:71
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:144
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the axis' properties from an XML element.
Definition: qgsplot.cpp:81
void setGridMajorSymbol(QgsLineSymbol *symbol)
Sets the symbol used to render the major lines in the axis grid.
Definition: qgsplot.cpp:139
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:109
QgsTextFormat textFormat() const
Returns the text format used for the axis labels.
Definition: qgsplot.cpp:154
void setGridIntervalMajor(double interval)
Sets the interval of major grid lines for the axis.
Definition: qgsplot.h:125
void setLabelSuffixPlacement(Qgis::PlotAxisSuffixPlacement placement)
Sets the placement for the axis label suffixes.
Definition: qgsplot.cpp:129
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:134
void setLabelSuffix(const QString &suffix)
Sets the axis label suffix.
Definition: qgsplot.cpp:119
void setTextFormat(const QgsTextFormat &format)
Sets the text format used for the axis labels.
Definition: qgsplot.cpp:159
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:149
QgsNumericFormat * numericFormat() const
Returns the numeric format used for the axis labels.
Definition: qgsplot.cpp:104
Qgis::PlotAxisSuffixPlacement labelSuffixPlacement() const
Returns the placement for the axis label suffixes.
Definition: qgsplot.cpp:124
QString labelSuffix() const
Returns the axis label suffix, or an empty string if no label suffix is to be used.
Definition: qgsplot.cpp:114
static QgsFillSymbol * chartBorderSymbol()
Returns the default fill symbol to use for the chart area border.
Definition: qgsplot.cpp:805
static QgsNumericFormat * axisLabelNumericFormat()
Returns the default numeric format to use for plot axis labels.
Definition: qgsplot.cpp:780
static QgsLineSymbol * axisGridMinorSymbol()
Returns the default line symbol to use for axis minor grid lines.
Definition: qgsplot.cpp:792
static QgsFillSymbol * chartBackgroundSymbol()
Returns the default fill symbol to use for the chart area background fill.
Definition: qgsplot.cpp:799
static QgsLineSymbol * axisGridMajorSymbol()
Returns the default line symbol to use for axis major grid lines.
Definition: qgsplot.cpp:785
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:877
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
Definition: qgssymbol.cpp:829
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.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition: qgis.h:5417
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:5124
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition: qgis.h:5398
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:30
Single variable definition for use within a QgsExpressionContextScope.